Куда попадают асинхронные задачи в Dart?

Ответ

Асинхронные задачи в Dart планируются и выполняются Event Loop'ом, который управляет двумя очередями:

  1. Очередь микрозадач (Microtask Queue) — для задач с высшим приоритетом, которые должны выполниться как можно скорее, до следующей задачи из очереди событий.
  2. Очередь событий (Event Queue) — для стандартных асинхронных операций: таймеров (Future.delayed), ввода-вывода (сеть, файлы), взаимодействия с UI, и задач, созданных через Future(() => ...).

Порядок выполнения на практике:

void demonstrateEventLoop() async {
  print('1. Синхронный код выполняется сразу');

  Future(() => print('6. Задача из Event Queue (обычный Future)'));

  scheduleMicrotask(() => print('3. Микрозадача 1'));

  Future.microtask(() => print('4. Микрозадача 2'));

  Future.delayed(Duration.zero, () => print('5. Future с нулевой задержкой тоже в Event Queue'));

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

  // Вывод будет: 1, 2, 3, 4, 5, 6
}

Как это влияет на разработку в Flutter:

  • scheduleMicrotask полезен, когда нужно отложить выполнение кода, но гарантировать, что он выполнится до следующего кадра отрисовки или обработки пользовательского ввода.
  • Опасность блокировки: Длительная синхронная операция внутри микрозадачи заблокирует обработку событий (включая отрисовку UI), что приведёт к "зависанию" интерфейса.
  • Правило: Тяжёлые вычисления лучше выносить в изолят (Isolate) или, как минимум, в обычную очередь событий через Future, чтобы не блокировать цикл обработки микрозадач.

Ответ 18+ 🔞

А, блядь, вот это тема! Сейчас разжуем, как этот ваш Dart под капотом работает, а то народ потом удивляется, почему UI виснет, а анимация дергается, как будто на дворе 2002-й год.

Смотри сюда, у нас тут главный по тарелочкам — Event Loop, он же цикл событий. Мужик серьёзный, у него две корзины, куда он задачи складывает, и он их по строгому распорядку разгребает. Ёпта, порядок — наше всё.

Первая корзина — Очередь микрозадач (Microtask Queue). Это VIP-зал, блядь. Задачи туда попадают — и им немедленно зелёный свет, прямо сейчас, до того как он к обычной очереди даже прикоснётся. Туда лезут через scheduleMicrotask() или Future.microtask(). Представь, что это срочный вызов, типа «сейчас же убери ноги с моего стола».

Вторая — Очередь событий (Event Queue). Это уже общая приёмная. Сюда стекается всё остальное: таймеры (Future.delayed), ответы от сети, клики по кнопкам, и просто ленивые задачи через Future(() => ...). Ждут своей очереди, как в поликлинике.

А теперь смотри, как это на практике выглядит, чтобы ни хуя себе не было:

void demonstrateEventLoop() async {
  print('1. Синхронный код выполняется сразу'); // Тут всё ясно, да?

  Future(() => print('6. Задача из Event Queue (обычный Future)')); // Кинули в общую очередь, жди

  scheduleMicrotask(() => print('3. Микрозадача 1')); // Ага, VIP! Выполнится сразу после синхронщины

  Future.microtask(() => print('4. Микрозадача 2')); // Тоже VIP, следом

  Future.delayed(Duration.zero, () => print('5. Future с нулевой задержкой тоже в Event Queue')); // Ноль секунд — не значит «сейчас», значит «в общую очередь, браток»

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

  // Вывод будет: 1, 2, 3, 4, 5, 6
}

Видишь магию? Сначала весь синхронный код — раз-два. Потом Event Loop смотрит: «О, микрозадачи есть!» — и выгребает их все до одной. Только потом, когда VIP-зал пуст, он начинает тащить задачи из общей очереди событий. Поэтому «обычный Future» оказался самым последним, хоть и был объявлен раньше всех.

А теперь, чувак, главное — как этим не выстрелить себе в ногу во Flutter:

  • scheduleMicrotask — это как быстрый пинок. Используй, когда тебе надо гарантированно что-то сделать прямо сейчас, но после текущего куска кода, и до того как Flutter возьмётся за отрисовку следующего кадра или обработает новый клик. Например, обновить состояние прямо перед построением виджета.
  • Опасность блокировки — вот она, сука, главная ловушка. Если ты в эту VIP-очередь микрозадач засунешь какую-нибудь тяжелую синхронную хрень (типа перебора миллиона элементов, сложных вычислений), то ты полностью заблокируешь Event Loop. Он будет сидеть и ебаться с твоей задачей, а очередь событий, где как раз таймеры анимаций и отклик UI, будет просто стоять и ждать. И пользователь получит зависший интерфейс. Доверия ебать ноль к такому коду.
  • Правило простое, как три копейки: Всё, что требует времени и сил — выноси нахуй из микрозадач. Лучше всего — в изолят (Isolate), это отдельный поток вычислений. Если лень с изолятами возиться, хотя бы кидай в обычную очередь событий через Future(() => твоя_тяжелая_херня). Пусть работает в фоне, не мешая главному циклу крутиться. Помни: Event Loop должен быть быстрым, как хуй с горы, иначе всем будет пиздец.