Dart 2.15 更新后 isolate 应该这么用

序言

2021年的最后一天, Dart 官方发布了 dart 2.15 版本,该版本优化了很多内容,今天我们要重点说说 isolate 工作器。官方推文链接

在探索新变化之前,我们来回忆巩固一下 isolate 的使用。

isolate 的作用

问题:Flutter 基于单线程模式使用协程进行开发,为什么还需要 isolate

首先我们要明确 并行(isolate)并发(future)的区别。下面我们通过简单的例子来进行说明 。Demo 是一个简单的页面,中间放置一个不断转圈的 progress 和一个按键,按键用来触发耗时方法。

///计算偶数个数(具体的耗时操作)下面示例代码中会用到
static int calculateEvenCount(int num) {
    int count = 0;
    while (num > 0) {
      if (num % 2 == 0) {
        count++;
      }
      num--;
    }
    return count;
  }
///按键点击事件
onPressed: () {
      //触发耗时操作
      doMockTimeConsume();
  }
  • 方式一: 我们将耗时操作使用 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(() {});
  }

结果如下:


结论:使用 future 的方式来消费耗时操作,由于仍然是单线程在进行工作,异步只是在同一个线程的并发操作,仍会阻塞UI的刷新。

  • 方式二: 使用 isolate 开辟新线程,避开主线程,不干扰UI刷新
//模拟耗时操作
  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(List<dynamic> args) {
    SendPort responsePort = args[0];
    int num = args[1];
    ///接收参数,进行耗时操作后返回数据
    responsePort.send(calculateEvenCount(num));
  }

结果如下:


结论:使用 isolate 实现了多线程并行,在新线程中进行耗时操作不会干扰UI线程的刷新。

isolate 的局限性,为什么需要优化?

iso 有两点较为重要的局限性。

  • isolate 消耗较重,除了创建耗时,每次创建还至少需要2Mb的空间,有OOM的风险。
  • isolate 之间的内存空间各自独立,当参数或结果跨 iso 相互传递时需要深度拷贝,拷贝耗时,可能造成UI卡顿。

isolate 新特性

Dart 2.15 更新, 给 iso 添加了组的概念,isolate 组 工作特征可简单总结为以下两点:

  • Isolate 组中的 isolate 共享各种内部数据结构
  • Isolate 组仍然阻止在 isolate 间共享访问可变对象,但由于 isolate 组使用共享堆实现,这也让其拥有了更多的功能。

官方推文中举了一个例子

工作器 isolate 通过网络调用获得数据,将该数据解析为大型 JSON 对象图,然后将这个 JSON 图返回到主 isolate 中。

Dart 2.15 之前:执行该操作需要深度复制,如果复制花费的时间超过帧预算时间,就会导致界面卡顿。

使用 Dart 2.15:工作器 isolate 可以调用 Isolate.exit(),将其结果作为参数传递。然后,Dart 运行时将包含结果的内存数据从工作器 isolate 传递到主 isolate 中,无需复制,且主 isolate 可以在固定时间内接收结果。

重点:提供 Isolate.exit() 方法,将包含结果的内存数据从工作器 isolate 传递到主 isolate ,过程无需复制。

附注: 使用 Dart 新特性,需将 flutter sdk 升级到 2.8.0 以上 链接

exit 和 send 的区别及用法

Dart 更新后,我们将数据从 工作器 isolate(子线程)回传到 主 isolate(主线程)有两种方式。

  • 方式一: 使用 send
responsePort.send(data);

点击进入 send 方法查看源码注释,看到这样一句话:

结论:send 本身不会阻塞,会立即发送,但可能需要线性时间成本用于复制数据。

  • 方式二:使用 exit
Isolate.exit(responsePort, data);

官网 给出的解释如下:

结论:隔离之间的消息传递通常涉及数据复制,因此可能会很慢,并且会随着消息大小的增加而增加。但是 exit(),则是在退出隔离中保存消息的内存,不会被复制,而是被传输到主 isolate。这种传输很快,并且在恒定的时间内完成。

我们把上面 demo 中的 _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,可规避数据复制,节省耗时。

isolate 组

如何创建一个 isolate 组?官方给出的解释如下:

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.

当在 isolate 中调用另一个 isolate 时,这两个 isolate 具有相同的可执行代码,并且位于同一隔离组。
PS: 小轰暂时也没有想到具体的使用场景,先暂放一边吧。

实践:isolate 如何处理连续数据

结合上面的耗时方法calculateEvenCountisolate 处理连续数据需要结合 stream 流 的设计。具体 demo 如下:

///测试入口
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;
      }
    }
  }

抛砖引玉,这只是一个思路~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容