Вот как следует использовать изоляцию после обновления Dart 2.15

Flutter

Преамбула

Последний день 2021 года,Dartофициально выпущенdart 2.15версия, эта версия оптимизирует много контента, сегодня мы хотим сосредоточиться наisolateрабочий.Официальная ссылка на твит

Прежде чем исследовать новые изменения, давайте вспомним и закрепимisolateиспользование.

Роль изолята

вопрос:FlutterИспользование сопрограмм для разработки на основе однопоточного режима, зачем вам это нужно?isolate?

Сначала мы должны уточнить并行(isolate)а также并发(future)разница. Ниже мы проиллюстрируем это на простом примере. Демо — это простая страница с кругом посередине.progressи ключ, который запускает трудоемкий метод.

///计算偶数个数(具体的耗时操作)下面示例代码中会用到
static int calculateEvenCount(int num) {
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }
///按键点击事件
onPressed: () {
      //触发耗时操作
      doMockTimeConsume();
  }
  • Способ 1: мы будем использовать трудоемкую операциюfutureспособ упаковки
///使用future的方式封装耗时操作
static Future<int> futureCountEven(int num) async {
    var result = calculateEvenCount(num);
    return Future.value(result);
  }

///耗时事件
void doMockTimeConsume() async {
    var result = await futureCountEven(1000000000);
    _count = result;
    setState(() {});
  }

Результат выглядит следующим образом:

1641368612690.gifВывод: используйтеfutureСпособ использования трудоемких операций, поскольку это по-прежнему один поток работы, асинхронный — это только параллельная операция в том же потоке, которая по-прежнему будет блокировать обновление пользовательского интерфейса.

  • Способ 2: ИспользованиеisolateСоздайте новый поток, избегайте основного потока, не мешайте обновлению пользовательского интерфейса
//模拟耗时操作
  void doMockTimeConsume() async {
    var result = await isolateCountEven(1000000000);
    _count = result;
    setState(() {});
  }
  ///使用isolate的方式封装耗时操作
  static Future<dynamic> isolateCountEven(int num) async {
    final p = ReceivePort();
    ///发送参数
    await Isolate.spawn(_entryPoint, [p.sendPort, num]);
    return (await p.first) as int;
  }

 static void _entryPoint(SendPort port) {
    SendPort responsePort = args[0];
    int num = args[1];
    ///接收参数,进行耗时操作后返回数据
    responsePort.send(calculateEvenCount(num));
  }

Результат выглядит следующим образом:

1641368546011.gifВывод: используйтеisolateРеализован многопоточный параллелизм, а длительные операции в новом потоке не будут мешать обновлению UI-потока.

Ограничения изоляции, зачем вам оптимизация?

iso имеет еще два важных ограничения.

  • Изолировать требует много денег.В дополнение к трудоемкому созданию, каждое создание требует не менее 2 МБ места, что может привести к OOM.
  • Пространство памяти между изолятами не зависит друг от друга. Когда параметры или результаты передаются друг другу через ISO, требуется глубокая копия. Копирование занимает много времени и может привести к зависанию пользовательского интерфейса.

выделять новые функции

Обновление Dart 2.15, добавлено понятие групп в iso,isolate 组Рабочие характеристики можно кратко свести к следующим двум пунктам:

  • Изоляты в группе Изолировать имеют различные внутренние структуры данных.
  • Изолировать группу仍然阻止Общий доступ к изменяемым объектам между изолятами, но поскольку группы изолятов реализованы с использованием общих куч, это также позволяет им иметь больше функций.

Пример приведен в официальном твите:

Рабочий изолят получает данные через сетевые вызовы, анализирует данные в большой граф объектов JSON и возвращает этот граф JSON в основной изолят.

Dart 2.15 之前: Для выполнения этой операции требуется глубокая копия.Если время копирования превышает время бюджета кадра, интерфейс зависает.

使用 Dart 2.15 : Рабочий изолировать может позвонитьIsolate.exit()проходя результат в качестве параметра. Затем Dart Runtime передает данные памяти, содержащие результат от рабочего изолята до основного изолята без копирования, а основной изолят может получить результат в течение непосредственного времени.

Ключ: обеспечениеIsolate.exit()способ передачи данных памяти, содержащих результат, из рабочего изолята в основной изолят без копирования процесса.

Примечание. Чтобы использовать новые функции Dart, вам необходимо обновить flutter sdk до версии 2.8.0 или выше.Связь.

Отдельно: разница и использование выхода и отправки

После обновления Dart мы меняем данные с工作器 isolate(子线程)Вернуться к主 isolate(主线程)Есть два способа.

  • Способ 1: Использованиеsend
responsePort.send(data);

нажмите, чтобы войтиsendМетод просматривает комментарии к исходному коду и видит это предложение:

image.png

В заключение:sendНе блокирует сам по себе, отправляет немедленно, но может потребовать линейных временных затрат на копирование данных.

  • Способ 2: Использованиеexit
Isolate.exit(responsePort, data);

Официальный сайтДается следующее объяснение:

image.png

Вывод. Передача сообщений между изолятами обычно включает копирование данных, поэтому может быть медленным и увеличиваться с увеличением размера сообщения. ноexit(), память, которая сохраняет сообщение в выходной изоляции, не будет скопирована, а будет передана основной изоляции. Эта передача является быстрой и завершается за постоянное время.

Давайте оптимизируем метод _entryPoint в приведенной выше демонстрации и изменим его следующим образом:

 static void _entryPoint(SendPort port) {
    SendPort responsePort = args[0];
    int num = args[1];
    ///接收参数,进行耗时操作后返回数据
    //responsePort.send(calculateEvenCount(num));
    Isolate.exit(responsePort, calculateEvenCount(num));
  }

Резюме: использоватьexit()заменятьSendPort.send, что позволяет избежать дублирования данных и сэкономить время.

изолировать группу

Как создать изолированную группу? Официальное объяснение следующее:

When an isolate calls Isolate.spawn(), the two isolates have the same executable code and are in the same isolate group. Isolate groups enable performance optimizations such as sharing code; a new isolate immediately runs the code owned by the isolate group. Also, Isolate.exit() works only when the isolates are in the same isolate group.

Когда другой изолят вызывается из изолята, два изолята имеют один и тот же исполняемый код и находятся в одной и той же группе изоляции.

PS: Сяохун пока не придумал конкретный сценарий использования, так что давайте пока отложим его.

Практическое занятие: как изолировать работу с непрерывными данными

Сочетание вышеуказанного трудоемкого методаcalculateEvenCount,isolateРабота с непрерывными данными требует объединенияпоток потокдизайн. Конкретная демонстрация выглядит следующим образом:

///测试入口
static testContinuityIso() async {
    final numbs = [10000, 20000, 30000, 40000];
    await for (final data in _sendAndReceive(numbs)) {
      log(data.toString());
    }
  }
///具体的iso实现(主线程)
static Stream<Map<String, dynamic>> _sendAndReceive(List<int> numbs) async* {
    final p = ReceivePort();
    await Isolate.spawn(_entry, p.sendPort);
    final events = StreamQueue<dynamic>(p);

    // 拿到 子isolate传递过来的 SendPort 用于发送数据
    SendPort sendPort = await events.next;
    for (var num in numbs) {
      //发送一条数据,等待一条数据结果,往复循环
      sendPort.send(num);
      Map<String, dynamic> message = await events.next;
      //每次的结果通过stream流外露
      yield message;
    }
    //发送 null 作为结束标识符
    sendPort.send(null);
    await events.cancel();
  }
///具体的iso实现(子线程)
static Future<void> _entry(SendPort p) async {
    final commandPort = ReceivePort();
    //发送一个 sendPort 给主iso ,用于 主iso 发送参数给 子iso
    p.send(commandPort.sendPort);
    await for (final message in commandPort) {
      if (message is int) {
        final data = calculateEvenCount(message);
        p.send(data);
      } else if (message == null) {
        break;
      }
    }
  }

Это просто идея~