Как устроена асинхронность в Dart?

«Как устроена асинхронность в Dart?» — вопрос из категории Асинхронность, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Асинхронность в Dart построена вокруг Event Loop, Futures и Streams. Это однопоточная модель, которая не блокирует выполнение на операциях ввода-вывода (I/O).

1. Основные абстракции

  • Future<T>: Представляет результат отложенной (асинхронной) операции, которая завершится одним значением (или ошибкой) в будущем.
  • Stream<T>: Представляет последовательность асинхронных событий (данных или ошибок) во времени.

2. Event Loop и Очереди

Dart runtime управляет двумя очередями:

  1. Microtask Queue: Для высокоприоритетных задач (например, завершение Future через completer.complete()).
  2. Event Queue: Для внешних событий (таймеры, I/O, жесты, отрисовка кадров).

Event Loop постоянно работает: сначала выполняет все задачи из Microtask Queue, затем одну задачу из Event Queue, и так по кругу. Это предотвращает «голодание» событий.

3. Практическое использование

С Future и async/await:

Future<String> fetchUserData() async {
  // Имитация сетевого запроса.
  await Future.delayed(Duration(seconds: 1));
  // Имитация декодирования JSON.
  return '{"name": "Alice"}';
}

void main() async {
  print('Fetching data...');
  try {
    final jsonString = await fetchUserData(); // Не блокирует поток.
    final user = jsonDecode(jsonString);
    print('User: ${user['name']}');
  } catch (e) {
    print('Error: $e');
  }
  print('This runs after data is fetched.');
}

Работа со Stream:

Stream<int> timedCounter(Duration interval, int maxCount) async* {
  int count = 0;
  while (count < maxCount) {
    await Future.delayed(interval);
    yield ++count; // «Выдаем» значение в поток.
  }
}

void main() async {
  final stream = timedCounter(Duration(seconds: 1), 5);
  await for (final count in stream) {
    print('Count: $count'); // Печатает 1, 2, 3, 4, 5 с интервалом в секунду.
  }
}

4. Параллелизм: Isolates

Для выполнения CPU-интенсивных задач, чтобы не блокировать Event Loop, используются Isolates. Это отдельные потоки выполнения с собственной памятью, общающиеся через передачу сообщений.

void heavyComputation(SendPort sendPort) {
  // Долгий расчет в отдельном изоляте.
  final result = _calculatePi(100000);
  sendPort.send(result);
}

void main() async {
  final receivePort = ReceivePort();
  await Isolate.spawn(heavyComputation, receivePort.sendPort);
  receivePort.listen((message) {
    print('Result from isolate: $message');
    receivePort.close();
  });
}

Итог: Dart использует неблокирующую асинхронную модель на основе событий, что идеально подходит для I/O-задач в Flutter (сеть, файлы, БД), сохраняя интерфейс плавным.