Что такое изоляты (Isolates) в Dart и как они связаны с асинхронностью?

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

Ответ

Изоляты (Isolates) в Dart — это не корутины, а отдельные потоки выполнения с собственной памятью (кучей). Это ключевая модель параллелизма в Dart/Flutter, кардинально отличающаяся от потоков с общей памятью в Java/Kotlin или корутин в Kotlin.

Основная концепция: "Не делиться памятью, обмениваться сообщениями".

Почему это важно для Flutter-разработчика?

  1. Главный изолят (UI Thread): Весь код Flutter-приложения по умолчанию выполняется в главном изоляте. Он отвечает за отрисовку UI, обработку жестов и выполнение вашего Dart-кода. Долгая синхронная операция в нём заблокирует интерфейс (вызовет "jank").

  2. Асинхронность (async/await, Future, Stream) решает проблему ожидания без блокировки (например, сетевой запрос), но не создаёт параллелизма. Асинхронный код всё равно выполняется в главном изоляте, просто он умеет эффективно ждать, пока работают внешние системы (ОС, сетевой стек).

  3. Когда нужны изоляты? Когда требуется тяжёлое вычисление (сортировка огромного списка, обработка изображения, сложная математика), которое может занять >16мс и привести к пропуску кадра.

Практический пример: Вычисление числа Фибоначчи в фоне.

// main.dart - Главный изолят
import 'dart:isolate';

void main() async {
  print('UI Isolate: Started');

  final receivePort = ReceivePort();

  // 1. Создаём новый изолят, передавая ему SendPort для ответа
  await Isolate.spawn(
    _fibonacciIsolate, // Функция, которая запустится в новом изоляте
    receivePort.sendPort, // Аргумент для этой функции
  );

  // 2. Подписываемся на сообщения от нового изолята
  receivePort.listen((message) {
    if (message is int) {
      print('UI Isolate: Received result: $message');
      // Здесь можно обновить UI через setState
    }
    receivePort.close(); // Закрываем порт после получения результата
  });

  print('UI Isolate: Waiting for result (UI не блокируется!)...');
}

// 3. Эта функция выполняется В ОТДЕЛЬНОМ ИЗОЛЯТЕ
void _fibonacciIsolate(SendPort sendPort) {
  print('Worker Isolate: Started heavy computation...');
  // Долгое вычисление (не блокирует UI!)
  int result = _computeFibonacci(40);
  print('Worker Isolate: Computation finished.');
  // 4. Отправляем результат обратно в главный изолят
  sendPort.send(result);
}

// Синхронная функция, имитирующая нагрузку
int _computeFibonacci(int n) {
  if (n <= 1) return n;
  return _computeFibonacci(n - 1) + _computeFibonacci(n - 2);
}

Альтернативы для упрощения: Для большинства задач проще использовать пакеты, абстрагирующие работу с изолятами:

  • compute(): Глобальная функция Flutter для запуска одной функции в изоляте.
    final result = await compute(_computeFibonacci, 40);
  • IsolateNameServer: Для создания "фоновых сервисов" с возможностью повторного вызова.
  • Пакеты: flutter_isolate, workmanager для более сложных сценариев.

Вывод: Асинхронность (Future) — для операций ввода-вывода (I/O) без блокировки UI. Изоляты — для истинного параллелизма и выгрузки CPU-интенсивных задач с главного потока.