Что относится к макрозадачам (macrotask) в Dart?

Ответ

В Dart макрозадачи (macrotasks) — это задачи, которые планируются в очередь событий (event queue) и выполняются в следующих итерациях Event Loop. Они имеют более низкий приоритет, чем микрозадачи (microtasks).

Типичные примеры макрозадач:

  • Обработка внешних событий: Пользовательский ввод (тапы, жесты), таймеры (Timer.run, Future.delayed), сообщения от операционной системы.
  • I/O-операции: Завершение сетевого запроса, чтение/запись файла.
  • Завершение Future: Коллбэк, переданный в Future(() => ...) или then() (если Future еще не завершен), выполняется как макрозадача.

Порядок выполнения Event Loop:

  1. Выполняется синхронный код до конца.
  2. Обрабатывается вся очередь микрозадач (microtask queue) до опустошения.
  3. Берется и выполняется одна задача из очереди событий (event queue).
  4. Возврат к шагу 2 (проверка микрозадач).

Пример, демонстрирующий порядок:

void main() {
  print('1. Start (синхронный код)');

  // Помещается в очередь событий (макрозадача)
  Future(() => print('4. Future (macrotask from event queue)'));

  // Помещается в очередь микрозадач
  scheduleMicrotask(() => print('3. Microtask'));

  // Синхронный код продолжает выполняться
  print('2. End (синхронный код)');

  // Event Loop начнет работу после завершения main()
}

Вывод:

1. Start (синхронный код)
2. End (синхронный код)
3. Microtask
4. Future (macrotask from event queue)

Ответ 18+ 🔞

А, ну это же про Event Loop в Dart! Ёпта, вещь, которая сначала кажется простой, а потом такую мозгоправку устраивает, что сам от себя охуеешь. Слушай, представь себе такую картину.

Есть у тебя этот самый Event Loop — типа главный диспетчер, который решает, что и когда выполнять. И у него две основные очереди: одна для микрозадач (microtasks), а другая — для макрозадач (macrotasks). Вот макрозадачи — это как раз те самые, которые попадают в очередь событий (event queue) и выполняются не сразу, а в следующих итерациях цикла. Приоритет у них, скажем так, пониже, чем у микрозадач.

Что обычно туда летит, в эту очередь макрозадач? Да всякая классика, которую ты уже сто раз видел:

  • Всякий внешний шум: Пользователь ткнул в экран, таймер сработал (Timer.run, Future.delayed), система прислала какую-то фигню.
  • Ввод-вывод: Сетевой запрос приполз, файл прочитался.
  • Завершение Future: Коллбэк от Future(() => ...) или от then(), если Future ещё не был готов, — тоже макрозадача.

А теперь, блядь, самое важное — порядок работы этого цикла. Запоминай, а то потом будешь сидеть и думать, почему у тебя всё выполняется не в том порядке, в каком ты ожидал.

  1. Сначала он выгребает весь синхронный код до самого конца. Пока не упрётся.
  2. Потом он лезет в очередь микрозадач и выпиливает всё до дна, пока она не опустеет. Это железно.
  3. Только после этого он берёт одну-единственную задачу из очереди событий (то есть макрозадачу) и выполняет её.
  4. А потом — о, хитрая жопа! — он снова возвращается к шагу 2, проверяет микрозадачи, и если они там накопились, опять их все вычищает. И так по кругу.

Вот тебе наглядный пример, чтобы в голове всё встало на свои места:

void main() {
  print('1. Start (синхронный код)');

  // Эта штука полетит в очередь событий (макрозадача)
  Future(() => print('4. Future (macrotask from event queue)'));

  // А эта — прямиком в очередь микрозадач
  scheduleMicrotask(() => print('3. Microtask'));

  // Синхронный код продолжает работать
  print('2. End (синхронный код)');

  // И только когда main() полностью отработает, Event Loop начнёт свою движуху.
}

И что же мы увидим на выходе? Вот это:

1. Start (синхронный код)
2. End (синхронный код)
3. Microtask
4. Future (macrotask from event queue)

Вот и вся магия. Сначала весь синхронный код, потом ВСЕ микрозадачи, и только потом по одной начинают вытаскиваться макрозадачи. Запомни эту последовательность, и половина твоих будущих "а оно нихуя не работает как я задумал" отпадёт сама собой.