Что такое изоляты (Isolates) в Dart и зачем они нужны?

Ответ

Изоляты (Isolates) — это модель параллельных вычислений в Dart, основанная на передаче сообщений. Каждый изолят имеет свою собственную выделенную память (кучу) и работает в отдельном потоке. Ключевая особенность — отсутствие разделяемой памяти, что исключает состояние гонки (race conditions) и необходимость в блокировках (locks).

Зачем они нужны в Flutter? Для выполнения ресурсоемких задач (тяжелые вычисления, обработка изображений, парсинг больших JSON) без блокировки основного UI-потока.

Пример: Вычисление числа Фибоначчи в отдельном изоляте.

import 'dart:isolate';

// Функция, которая будет запущена в изоляте
void _fibonacciIsolate(SendPort sendPort) {
  // Принимаем порт для ответа
  final receivePort = ReceivePort();
  sendPort.send(receivePort.sendPort);

  // Ожидаем сообщение с данными для расчета
  receivePort.listen((message) {
    if (message is int) {
      int result = _calculateFibonacci(message);
      // Отправляем результат обратно в главный изолят
      sendPort.send(result);
    }
  });
}

int _calculateFibonacci(int n) {
  if (n <= 1) return n;
  return _calculateFibonacci(n - 1) + _calculateFibonacci(n - 2);
}

// Использование в основном потоке Flutter
Future<void> computeHeavyTask() async {
  final receivePort = ReceivePort();

  // Создаем изолят
  await Isolate.spawn(_fibonacciIsolate, receivePort.sendPort);

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

  // Отправляем задание (вычислить 40-е число Фибоначчи)
  childSendPort.send(40);

  // Ожидаем результат
  receivePort.listen((result) {
    if (result is int) {
      print('Результат из изолята: $result');
      // Обновляем UI через setState или провайдер
      // setState(() => _result = result);
      receivePort.close(); // Закрываем порт
    }
  });
}

Альтернативы для Flutter: Для большинства задач проще использовать compute() — высокоуровневую обертку над изолятами.

int fib(int n) => n <= 1 ? n : fib(n-1) + fib(n-2);

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

Ответ 18+ 🔞

А, изоляты в Dart! Ну, это такая штука, чтобы твой интерфейс не зависал, пока какая-нибудь тяжёлая хрень в фоне работает. Представь: ты в приложении кнопку нажал, а оно на три секунды в анабиоз уходит — терпения ноль ебать. Так вот, чтобы такого не было, и придумали эти изоляты.

Что это вообще такое? Это как будто ты нанял отдельного работягу на удалёнку, дал ему свою задачу и сказал: «Делай, братан, а как сделаешь — отзвонись». Главное правило — у этого работяги своя отдельная мастерская (память). Он не лезет в твой ящик с инструментами, а ты — в его. Поэтому они никогда не начнут драться из-за одной отвёртки — состояния гонки нет, блокировок не нужно. Красота!

Зачем в Flutter? Ну, ебушки-воробушки, причины очевидные. Всё, что долго и сложно: посчитать миллион цифр, обработать фото размером с твою совесть, распарсить JSON, который длиннее, чем список твоих грехов — всё это нужно выкидывать в изолят. А главный поток пусть рисует красивые кнопочки и анимации без тормозов.

Смотри, как это выглядит в коде. Вот тебе классика — число Фибоначчи. Если его в основном потоке считать для большого N, то интерфейс накроется медным тазом.

import 'dart:isolate';

// Это функция-работяга, которая будет жить в своём изоляте.
void _fibonacciIsolate(SendPort sendPort) {
  // Она создаёт свой «почтовый ящик» (ReceivePort), чтобы получать задания.
  final receivePort = ReceivePort();
  // И сразу кидает адрес этого ящика обратно начальнику (в главный изолят).
  sendPort.send(receivePort.sendPort);

  // Теперь сидит и слушает ящик: «Чё там начальник прислал?»
  receivePort.listen((message) {
    if (message is int) {
      // Ага, прислали число n. Ну, считаем.
      int result = _calculateFibonacci(message);
      // Готово! Отправляем результат наверх.
      sendPort.send(result);
    }
  });
}

// Сама функция расчёта. Рекурсия, всё как мы любим.
int _calculateFibonacci(int n) {
  if (n <= 1) return n;
  return _calculateFibonacci(n - 1) + _calculateFibonacci(n - 2);
}

// А это мы в основном потоке (допустим, во Flutter-виджете) вызываем.
Future<void> computeHeavyTask() async {
  // Создаём свой «почтовый ящик» для связи с работягой.
  final receivePort = ReceivePort();

  // Наняли работягу! Породили изолят.
  await Isolate.spawn(_fibonacciIsolate, receivePort.sendPort);

  // Ждём первое письмо от него — это будет адрес ЕГО ящика, куда нам слать задания.
  final SendPort childSendPort = await receivePort.first as SendPort;

  // Отправляем ему задание: «На, посчитай 40-е число Фибоначчи, не отвлекайся».
  childSendPort.send(40);

  // И слушаем свой ящик в ожидании ответа.
  receivePort.listen((result) {
    if (result is int) {
      print('Результат из изолята: $result'); // Опа, пришло!
      // Тут обычно обновляем состояние виджета (setState или провайдер).
      // setState(() => _result = result);
      receivePort.close(); // Задание выполнено, ящик закрываем.
    }
  });
}

Но если честно, постоянно возиться с портами — это ещё тот геморрой. Поэтому в Flutter есть готовая обёртка compute(). Это просто пидец как удобно!

// Объявляем обычную функцию. Без всяких портов!
int fib(int n) => n <= 1 ? n : fib(n-1) + fib(n-2);

void main() async {
  // А compute сам создаст изолят, запустит там функцию, передаст аргумент и вернёт Future с результатом.
  final result = await compute(fib, 40);
  print(result); // Всё, готово. Главный поток даже не вспотел.
}

Итог: Если нужно что-то тяжёлое — да похуй, не мучай основной поток. Кидай в compute(). Если задача овердохуища сложная и с кучей промежуточных сообщений — тогда уже лезь в ручное управление изолятами с портами. Но в 95% случаев compute() — твой лучший друг, чувак.