Изоляты

Последнее обновление: 11.04.2023

Тот факт, что приложение Dart работает в однопоточном режиме, не означает, что в принципе нельзя использовать другие потоки. В частности, можно создать новый изолят (isolate). Новый изолят будет иметь свою собственную память и собственный поток, который будет работать в другом потоке параллельно основному потоку приложения. Но стоит учитывать, что два изолята не имеют доступа к памяти друг друга и могут взаимодействовать только посредством сообщений.

При создании нового изолята ему предоставляется объект, который называется send port и который применяется для отправки сообщений. На изоляте главного потока для получения сообщений можно использовать объект receive port. При этом оба изолята могут как отправлять, так и получать сообщения.

Создание изолятов обычно применяется для вынесения в отдельный поток вычичлений, которые могут занять продолжительное время. Рассмотрим сначала примитивную задачу

void main() async{
  print("Start calculation");
  final result = count();
  print(result);
  print("Some work...");
}

int count() {
  var result = 0;
  for (var i = 1; i <= 10000000000; i++) {
    result = i;
  }
  return result;
}

Здесь у нас есть функция count, которая последовательно проходит по числам от 1 до 10000000000 и возвращает последнее число. Хотя эта функция несколько бесмысленна, тем не менее она позволяет имитировать некоторую долгую работу. И в реальности вместо нее могли бы быть реальные вычисления, например, какая-нибудь обработка изображений, видео, другие вычисления. И если это происходит в графическом приложении, где пользователь, нажав на кнопку, расчитывает получить результат, то приложение зависает в ожидании окончания вычисления.

Создание асинхронной версии функции не сильно изменит ситуацию:

void main() async{
  print("Start calculation");
  final result = await count();
  print(result);
  print("Some work...");
}

Future<int> count() async {
  var result = 0;
  await Future((){
    for (var i = 1; i <= 10000000000; i++) {
      result = i;
    }
  });
  return result;
}

Асинхронная операция в асинхронной функции count просто помещается в конец очереди событий Event Queue, и когда она начнет работать, то приложение аналогичным образом блокируется до завершения выполнения асинхронной операции.

Теперь используем изоляты:

import 'dart:isolate';

void main() async{
  print("Start calculation");
  // создаем порт приема сообщений от нового изолята
  final receivePort = ReceivePort();
  // создаем новый изолят
  final isolate = await Isolate.spawn(count, receivePort.sendPort);
  // запускаем прослушивание входящих сообщений
  receivePort.listen((message) {
    print(message);
    // изолят больше не нужен  - завершаем его
    receivePort.close();
    isolate.kill();
  });
  print("Some work...");
}

void count(SendPort sendPort) {
  var result = 0;
  for (var i = 1; i <= 10000000000; i++) {
    result = i;
  }
  sendPort.send(result);
}

Теперь функция count в качестве параметра принимает значение SendPort - порт отправки, через который будет отправляться сообщение. И после завершения всех вычислений при помощи метода sendPort.send() отправляется результат вычислений в изолят основного потока.

Для выполнения функции count в функции main начала создаем порт примема сообщений - объект ReceivePort

final receivePort = ReceivePort();

Далее с помошью статического метода Isolate.spawn создаем новый изолят, передавая в метод значения для двух обязательных параметров:

final isolate = await Isolate.spawn(count, receivePort.sendPort);

Первый параметр представляет функцию, которую изолят будет выполнять. А второй параметр - это объект, который передается в эту функцию. В нашем случае функция count принимает объект SendPort. И с помощью значения receivePort.sendPort мы можем передать в функцию требуемый порт отправки.

Если бы в функцию count надо было передать какие-то еще значения, то эти значения можно передавать в виде списка или словаря, либо создать специальный класс для передачи всех значений.

Затем для прослушивания входящих сообщений запускаем метод listen():

receivePort.listen((message) {
    print(message);
    receivePort.close();
    isolate.kill();
  });

В качестве параметра этот метод принимает функцию, которая принимает полученное от изолята сообщение. В данном случае мы просто выводим сообщение на консоль.

После того как мы получили результат функции во входящем сообщении, то есть функция count завершила выполнение, ее изолят нам больше не нужен. Поэтому закрываем порт получения сообщений

receivePort.close();

и завершаем работу изолята, созданного для функции count:

isolate.kill();

В итоге консольный вывод программы будет следующим:

Start calculation
Some work...
10000000000
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850