Какие инструменты для работы с многопоточностью в Dart/Flutter ты знаешь?

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

Ответ

В Dart/Flutter я работаю с многопоточностью через изоляты (isolates), так как Dart использует модель акторов с разделяемой-nothing архитектурой:

1. Базовые изоляты для CPU-intensive задач:

// Создание простого изолята
Future<void> runInIsolate() async {
  final receivePort = ReceivePort();

  await Isolate.spawn(
    _heavyComputation,
    receivePort.sendPort, // Передаем порт для коммуникации
  );

  // Получаем результат из изолята
  final result = await receivePort.first;
  print('Result from isolate: $result');
}

void _heavyComputation(SendPort sendPort) {
  // Этот код выполняется в отдельном изоляте
  final result = performExpensiveCalculation();
  sendPort.send(result);
}

2. Упрощенный API через compute():

// Для одноразовых задач
int fibonacci(int n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

void calculateFibonacci() async {
  // compute автоматически создает изолят, выполняет функцию
  // и возвращает результат
  final result = await compute(fibonacci, 40);
  print('Fibonacci(40) = $result');
}

3. Продвинутые сценарии с несколькими изолятами:

// Создание пула изолятов для параллельной обработки
class IsolatePool {
  final List<Isolate> _isolates = [];

  Future<void> initialize({int count = 4}) async {
    for (int i = 0; i < count; i++) {
      final isolate = await Isolate.spawn(_worker, i);
      _isolates.add(isolate);
    }
  }

  static void _worker(int id) {
    print('Isolate $id started');
    // Логика worker-изолята
  }
}

4. Коммуникация между изолятами:

// Двусторонняя коммуникация через порты
Future<SendPort> createTwoWayCommunication() async {
  final receivePort = ReceivePort();

  await Isolate.spawn(
    _isolateEntry,
    receivePort.sendPort,
  );

  // Получаем порт для отправки сообщений в изолят
  final sendPortToIsolate = await receivePort.first as SendPort;

  // Создаем порт для получения ответов
  final responsePort = ReceivePort();
  sendPortToIsolate.send({
    'message': 'Hello from main isolate',
    'replyTo': responsePort.sendPort,
  });

  return sendPortToIsolate;
}

5. Важные ограничения и best practices:

  • Изоляты не разделяют память, все данные должны быть сериализуемы
  • compute() не подходит для задач, требующих постоянной коммуникации
  • Для UI-обновлений из изолята используйте IsolateNameServer или StreamController с StreamBuilder
  • Избегайте создания большого количества изолятов (обычно 2-4 на CPU core)