一 . 原始代码
为什么要Isolate,我们先看一段比较简单的代码:

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';class TestWidget extends StatefulWidget {@overrideState<StatefulWidget> createState() {return TestWidgetState();}
}class TestWidgetState extends State<TestWidget> {int _count = 0;@overrideWidget build(BuildContext context) {return Material(child: Center(child: Column(children: <Widget>[Container(width: 100,height: 100,child: CircularProgressIndicator(),),FlatButton(onPressed: () async {_count = countEven(1000000000);setState(() {});},child: Text(_count.toString(),)),],mainAxisSize: MainAxisSize.min,),),);}//计算偶数的个数static int countEven(int num) {int count = 0;while (num > 0) {if (num % 2 == 0) {count++;}num--;}return count;}
}
UI包含两个部分,一个不断转圈的progress指示器,一个按钮,当点击按钮的时候,找出比某个正整数n小的数的偶数的个数(请忽视具体算法,故意做耗时计算用,哈哈)。我们来运行一下代码看看效果:可以看到,本来是很流畅的转圈,当我点击按钮计算的时候,UI出现了卡顿,为什么会出现卡顿,因为我们的计算默认是在UI线程中的,当我们调用countEven的时候,这个计算需要耗时,而在这期间,UI是没有机会去调用刷新的,因此会卡顿,计算完成后,UI恢复正常刷新。

二. 使用async优化
那么有些同学就会说了,在dart中,有async关键字,我们可以用异步计算,这样就不会影响UI的刷新了,事实真的是这样吗?我们一起来修改一下代码:

a. 将count改为asyncCountEven
  static Future<int> asyncCountEven(int num) async{int count = 0;while (num > 0) {if (num % 2 == 0) {count++;}num--;}return count;}
b. 调用:
_count = await asyncCountEven(1000000000);
我们继续运行一下代码,看现象:

仍然卡顿,说明异步是解决不了问题的,为什么?因为我们仍旧是在同一个UI线程中做运算,异步只是说我可以先运行其他的,等我这边有结果再返回,但是,记住,我们的计算仍旧是在这个UI线程,仍会阻塞UI的刷新,异步只是在同一个线程的并发操作。

三. 使用compute优化
那么我们怎么解决这个问题呢,其实很简单,我们知道卡顿的原因是在同一个线程中导致的,那我们有没有办法将计算移到新的线程中呢,当然是可以的。不过在dart中,这里不是称呼线程,是Isolate,直译叫做隔离,这么古怪的名字,是因为隔离不共享数据,每个隔离中的变量都是不同的,不能相互共享。

但是由于dart中的Isolate比较重量级,UI线程和Isolate中的数据的传输比较复杂,因此flutter为了简化用户代码,在foundation库中封装了一个轻量级compute操作,我们先看看compute,然后再来看Isolate。

要使用compute,必须注意的有两点,一是我们的compute中运行的函数,必须是顶级函数或者是static函数,二是compute传参,只能传递一个参数,返回值也只有一个,我们先看看本例中的compute优化吧:真的很简单,只用在使用的时候,放到compute函数中就行了。
_count = await compute(countEven, 1000000000);
再次运行,我们来看看效果吧:可以看到,现在的计算并不会导致UI卡顿,完美解决问题。

四. 使用Isolate优化
但是,compute的使用还是有些限制,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。在某些业务下,我们可以使用compute,但是在另外一些业务下,我们只能使用dart提供的Isolate了,我们先看看Isolate在本例中的使用:

 a. 增加这两个函数
  static Future<dynamic> isolateCountEven(int num) async {final response = ReceivePort();await Isolate.spawn(countEvent2, response.sendPort);final sendPort = await response.first;final answer = ReceivePort();sendPort.send([answer.sendPort, num]);return answer.first;}static void countEvent2(SendPort port) {final rPort = ReceivePort();port.send(rPort.sendPort);rPort.listen((message) {final send = message[0] as SendPort;final n = message[1] as int;send.send(countEven(n));});}
b. 使用
_count = await isolateCountEven(1000000000);
相对于compute复杂了很多,效果就不贴了,和compute一样,毫无卡顿。。

代价是什么

对于我们来说,其实是把多线程当做一种计算资源来使用的。我们可以通过创建新的 isolate 计算 heavy work,从而减轻 UI 线程的负担。但是这样做的代价是什么呢?

时间

通常来说,当我们使用多线程计算的时候,整个计算的时间会比单线程要多,额外的耗时是什么呢?

  • 创建 Isolate
  • Copy Message

当我们按照上面的代码执行一段多线程代码时,经历了 isolate 的创建以及销毁过程。下面是一种我们在解析 json 中这样编写代码可能的方式。

  static BSModel toBSModel(String json){}parsingModelList(List<String> jsonList) async{for(var model in jsonList){BSModel m = await compute(toBSModel, model);}}
复制代码

在解析 json 的时候,我们可能通过 compute 把解析任务放在新的 isolate 中完成,然后把值传过来。这时候我们会发现,整个解析会变得异常的慢。这是由于我们每次创建 BSModel 的时候都经历了一次 isolate 的创建以及销毁过程。这将会耗费约 50-150ms 的时间。

在这之中,我们传递 data 也经历了 Network -> Main Isolate -> New Isolate (result) -> Main Isolate,多出来两次 copy 的操作。如果我们是在 Main 线程之外的 isolate 下载的数据,那么就可以直接在该线程进行解析,最后只需要传回 Main Isolate 即可,省下了一次 copy 操作。(Network -> New Isolate (result)-> Main Isolate)

空间

Isolate 实际上是比较重的,每当我们创建出来一个新的 Isolate 至少需要 2mb 左右的空间甚至更多,取决于我们具体 isolate 的用途。

OOM 风险

我们可能会使用 message 传递 data 或 file。而实际上我们传递的 message 是经历了一次 copy 过程的,这其实就可能存在着 OOM 的风险。

如果说我们想要返回一个 2GB 的 data,在 iPhone X(3GB ram)上,我们是无法完成 message 的传递操作的。

Tips

上面已经介绍了使用 isolate 进行多线程操作会有一些额外的 cost,那么是否可以通过一些手段减少这些消耗呢。我个人建议从两个方向上入手。

  • 减少 isolate 创建所带来的消耗。
  • 减少 message copy 次数,以及大小。

使用 LoadBalancer

如何减少 isolate 创建所带来的消耗呢。自然一个想法就是能否创建一个线程池,初始化到那里。当我们需要使用的时候再拿来用就好了。

实际上 dart team 已经为我们写好一个非常实用的 package,其中就包括 LoadBalancer

我们现在 pubspec.yaml 中添加 isolate 的依赖。

isolate: ^2.0.2
复制代码

然后我们可以通过 LoadBalancer 创建出指定个数的 isolate。

Future<LoadBalancer> loadBalancer = LoadBalancer.create(2, IsolateRunner.spawn);
复制代码

这段代码将会创建出一个 isolate 线程池,并自动实现了负载均衡。

由于 dart 天生支持顶层函数,我们可以在 dart 文件中直接创建这个 LoadBalancer。下面我们再来看看应该如何使用 LoadBalancer 中的 isolate。

 int useLoadBalancer() async {final lb = await loadBalancer;int res = await lb.run<int, int>(_doSomething, 1);return res;}
复制代码

我们关注的只有 Future<R> run<R, P>(FutureOr<R> function(P argument), argument, 方法。我们还是需要传入一个 function 在某个 isolate 中运行,并传入其参数 argument。run 方法将会返回我们执行方法的返回值。

整体和 compute 使用感觉上差不多,但是当我们多次使用额外的 isolate 的时候,不再需要重复创建了。

并且 LoadBalancer 还支持 runMultiple,可以让一个方法在多线程中执行。具体使用请查看 api。

LoadBalancer 经过测试,它会在第一次使用其 isolate 的时候初始化线程池。

image.png

当应用打开后,即使我们在顶层函数中调用了 LoadBalancer.create,但是还是只会有一个 Isolate。

image.png

当我们调用 run 方法时,才真正创建出了实际的 isolate。

作者:三也视界
链接:https://www.jianshu.com/p/07b19f4752ea
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

flutter入门之理解Isolate及compute相关推荐

  1. flutter入门之理解Isolate及compute ——解决耗时操作卡住UI的问题

    这篇文章将会讲解flutter中的Isolate,这有助于帮你解决某些耗时计算问题导致的卡顿. 一 . 原始代码 为什么要Isolate,我们先看一段比较简单的代码: import 'package: ...

  2. Flutter 入门经典

    Flutter是Google公司推出的新一代前端框架,最初目标只是为了满足移动端跨平台的应用开发, 开发人员可使用 Flutter 在 iOS 和 Android 上快速构建高质量的原生用户界面.但如 ...

  3. flutter 入门示例_AnyChart入门— 10个实用示例

    flutter 入门示例 If your website is data-intensive, then you will need to make that data easy to visuali ...

  4. kubelet配置cni插件_从零开始入门 K8s | 理解 CNI 和 CNI 插件

    原标题:从零开始入门 K8s | 理解 CNI 和 CNI 插件 作者 | 溪恒 阿里巴巴高级技术专家 本文整理自<CNCF x Alibaba 云原生技术公开课>第 26 讲,点击直达课 ...

  5. Unity 新手入门 如何理解协程 IEnumerator yield

    Unity 新手入门 如何理解协程 IEnumerator 本文包含两个部分,前半部分是通俗解释一下Unity中的协程,后半部分讲讲C#的IEnumerator迭代器 协程是什么,能干什么? 为了能通 ...

  6. Flutter 入门指北(Part 9)之弹窗和提示(SnackBar、BottomSheet、Dialog)

    该文已授权公众号 「码个蛋」,转载请指明出处 前面的小节把常用的一些部件都介绍了,这节介绍下 Flutter 中的一些操作提示.Flutter 中的操作提示主要有这么几种 SnackBar.Botto ...

  7. Flutter入门进阶之旅(二)Hello Flutter

    开题 好像几乎我们学习或者掌握任何一门编程语言都是Hello word开始的,本篇博文做为Flutter入门进阶的第一篇分享,我们也从最简单的Hello world开始,至于Flutter开发环境的配 ...

  8. ROS2入门教程—理解话题(Topic)

    ROS2入门教程-理解话题(Topic) 1 启动小海龟仿真器 2 rqt_graph 3 ros2 topic list 4 ros2 topic echo 5 ros2 topic info 6 ...

  9. 从零开始的Flutter入门实战(二)

    目录 前言 一.Column布局 1.创建一个Column 2.添加Container 3.运行验证 二.Row布局 1.将Column改成Row 三.Column布局和Row布局的混合使用 1.Si ...

  10. Flutter入门——创建第一个Flutter项目

    Flutter入门--创建第一个Flutter项目 一.创建项目 第一个项目使用Android Studio创建,步骤如下: 先打开Android Studio,会有一个创建新的Flutter应用的选 ...

最新文章

  1. linux网卡开启GRO导致lvs 部分节点响应慢
  2. 关于宁波一些眼科流传的营养针
  3. android R.id.转化为view
  4. JavaScript内存管理——优化内存占用
  5. 数据库读写锁的C++实现
  6. 小米主办HBaseCon亚洲峰会,打造世界一流的“工程师理想乐园”
  7. python设计模式
  8. 接口测试工具apipost关于post请求
  9. 别再白瞎去花钱购买高精度卫星地图,一文教你解决精度与下载问题
  10. 高级设计总监的设计方法论——5W1H需求分析法 KANO模型分析法
  11. 一梦江湖获取服务器信息卡住,一梦江湖4月10日更新了什么 副本减负再临绝境天道盟开放...
  12. 对不起,这5类人都不适合自学编程
  13. 百行代码手撸扫雷(下)c/c++
  14. matlab曲面的最小值,在matlab中计算曲面的曲率
  15. 跨域的这三种解决方案你知道吗?
  16. 排列组合c几几怎么用计算机算,排列组合A几几C几几的,有什么区别,都怎么计算来的?...
  17. GloVe最全面、最深度的解析
  18. 离散数学笔记 - 手写 - 课堂笔记
  19. 【音频处理】音高 与 频率 对照表 ( 音符频率算法 )
  20. STM32——I2S简介硬件连接

热门文章

  1. 使用Scrapy(二)编写抓取规则
  2. javscript创建Emitter
  3. Bailian2706 麦森数【大数】
  4. 一个树莓派集群 (VAX)
  5. Java之spilt()函数,trim()函数
  6. 序:我的多旋翼飞控学习之路
  7. Java Lempel-Ziv
  8. 兄弟单词C语言,brother是什么意思
  9. su   sudo 命令
  10. wechat sdk java_使用java集成微信支付sdk。