Как работает event loop в Dart?
Dart event loop: сначала исполняется весь synchronous код, затем microtask queue (Future.then, scheduleMicrotask) до конца, потом одно событие из event queue (таймеры, I/O). Каждый Isolate имеет собственный loop.
Как устроен event loop в Dart
Dart — однопоточный язык с двумя очередями: event queue (макрозадачи) и microtask queue (микрозадачи). Это похоже на браузерный event loop, но без Web API — всё асинхронное идёт через Future и Stream.
Порядок обработки на каждой итерации:
- Выполнить всю microtask queue до конца (включая микрозадачи, добавленные во время выполнения).
- Взять одно событие из event queue и выполнить его.
- Снова опустошить microtask queue.
- Повторить.
Microtask queue
Заполняется через scheduleMicrotask() или когда Future уже завершён в момент подписки. Callback .then() на завершённом Future попадает в microtask queue, а не в event queue.
import 'dart:async';
void main() {
print('1: sync start');
Future.value('done').then((v) {
print('3: microtask — $v'); // Future уже готов → microtask
});
scheduleMicrotask(() => print('2: explicit microtask'));
print('4: sync end');
}
// Вывод:
// 1: sync start
// 4: sync end
// 2: explicit microtask
// 3: microtask — done
Обратите внимание: синхронный код исполняется первым, потом — все микрозадачи в порядке добавления.
Event queue
Содержит I/O-события, таймеры, порты isolate. Future.delayed добавляет задачу в event queue после истечения таймера.
import 'dart:async';
void main() {
Future.delayed(Duration.zero, () => print('B: event queue'));
scheduleMicrotask(() => print('A: microtask'));
print('sync');
}
// Вывод:
// sync
// A: microtask
// B: event queue
async/await под капотом
await — синтаксический сахар: приостанавливает выполнение функции и регистрирует продолжение как callback на Future. После await код выполняется в microtask queue (если Future уже завершён) или в event queue (когда I/O завершится).
Future<void> fetchData() async {
print('before await');
final result = await Future.delayed(
const Duration(milliseconds: 100),
() => 'data',
);
print('after await: $result'); // выполняется в event queue
}
void main() {
fetchData();
print('main continues'); // выполняется до 'after await'
}
Isolates и event loop
Каждый Isolate имеет собственный event loop и heap — изоляция памяти полная. Общение только через порты (SendPort/ReceivePort), сообщения сериализуются. Это принципиально отличается от worker threads в Node.js или Go goroutines.
import 'dart:isolate';
void heavyWork(SendPort sendPort) {
// Выполняется в отдельном isolate, свой event loop
final result = List.generate(1000000, (i) => i).fold(0, (a, b) => a + b);
sendPort.send(result);
}
Future<int> runInIsolate() async {
final receivePort = ReceivePort();
await Isolate.spawn(heavyWork, receivePort.sendPort);
return await receivePort.first as int;
}
Подводные камни
- Бесконечная microtask queue: если microtask порождает другой microtask — event queue никогда не обработается, UI зависнет. В Flutter это приводит к дропу фреймов.
- Блокировка event loop: синхронный CPU-интенсивный код (парсинг JSON, криптография) блокирует весь поток — нужен
Isolate.run()илиcompute()во Flutter. - Future.delayed(Duration.zero) не гарантирует немедленность: задача попадёт в event queue и выполнится после всех текущих microtasks и, возможно, других event queue задач.
- Порядок then()-callbacks: если несколько
.then()навешаны на один Future — они выполнятся в порядке добавления, все как microtasks. - Утечка через порты isolate: незакрытый
ReceivePortудерживает isolate живым — нужно явно вызыватьreceivePort.close(). - async не означает параллельность: два
awaitподряд — последовательные операции. Для параллелизма нуженFuture.wait([f1, f2])илиFuture.waitсIsolate. - StreamController без pause: если producer быстрее consumer и нет backpressure, буфер растёт бесконечно — используйте
StreamController(sync: false)и слушайтеonPause. - scheduleMicrotask в продакшене: прямое использование
scheduleMicrotaskредко оправдано — обычно достаточноFuture.microtask()для явности намерения.
Common mistakes
- Сводить «работает event loop в Dart» к синтаксису и не объяснять sound type system.
- Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии dart-10.
- Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.
What the interviewer is testing
- Формулирует точную модель для «работает event loop в Dart» и подтверждает ее корректным примером.
- Умеет связать ответ с event loop, тестированием и отладкой на устройстве.
- Называет ограничения подхода dart-10, включая производительность, память и сопровождение.