前提
- 众所周知,dart是单线程语言,通过事件循环EventLoop同时处理多个事件。但当单个事件会阻塞线程时,往往使用异步处理Future,但Future能力是有限的,若单个事件处理时间过长还是会影响整个线程运行导致卡顿(EventLoop事件循环),此时就需要isolate来处理。
- 为什么使用Future异步处理仍然会出现卡顿呢,因为 Dart 是单线程的,如果在执行 Future 时遇到耗时的计算任务或者 I/O操作,这些操作会占用当前线程的资源,从而导致应用出现卡顿现象,影响用户体验。
- isolate顾名思义 隔离,每个isolate有自己的堆内存,isolate之间是隔离的,不共享内存,只能通过消息机制共享数据。通常情况下,单个drat运行main函数时,都会自动生成一个主isolate,接着执行事件代码。
- 由此可知,当一个事件处理时间过长时,开启一个新的isolate并放该事件进去处理,最后返回给主事件,这样主事件就不会出现阻塞卡顿情况。因此,isolate又称为后台运行对象,即在后台运行事件,不会影响主线程。
isolate使用以及原理
ReceivePort
- 先讲isolate之间消息传播的原理ReceivePort(),生成一个实例var = ReceivePort(),里有个sendPort(),可以发送信息,发送到哪呢?
- 肯定是发送到自己的实例啦,即用.listen((){})就能监听到sendPort发送的数据。
- 由以上可得,在开启新的isolate前要先在实例化一个ReceivePort,然后把该对象的sendPort传进去,用来发送数据出来。
Isolate.spawn
- Isolate.spawn((message)=>dynamic,[args])
- Isolate.spawn用于生成新的isolate,并将事件代码传进去运行。第一个参数为传进去的函数(取名为),第二个参数【args】作为第一个参数的参数,即的参数,主要用于传sendPort和要处理的数据,其他参数例如onError、onExit需要传主isolate的sendPort,目的是捕捉异常。
- 具体例子如下:
import 'dart:isolate';
void main() {
SendPort childSendPort;
//创建新的具有发送器的isolate,第一个参数是具有内存隔离的新的isolate的具体业务逻辑函数,第二个是创建的isolate的时候传递的参数,一般我们传递当前isolate的发送器
ReceivePort receivePort = new ReceivePort();
Isolate.spawn(isolateVice, receivePort.sendPort);
//主进程接受持有主进程发送器的isolate发过来的消息
receivePort.listen((message) {
//其他的isolate可以选择发过来自身的sendPort给主进程,则主进程isolate也可以向创建的isolate发送消息,完成交互操作
if (message is SendPort) {
childSendPort = message;
message.send("———————— 已收到函数isolateVice()的发送器 ————————");
} else {
print("接到函数isolateVice()消息:" + message);
if (childSendPort != null) {
childSendPort.send('您喜欢我哪些,我改还不成嘛!'); //进行一次回复
}
}
});
}
/// 内存隔离的新的isolate的具体业务逻辑函数
void isolateVice(SendPort sendPort) {
ReceivePort receivePort = new ReceivePort(); //当前isolate的消息接收器
//创建当前函数(isolateVice)的时候传递的第二个参数(这里我们认为是该iso的发送器),使用主iso的发送器将自身子iso的发送器发送过去,完成交互
sendPort.send(receivePort.sendPort);
sendPort.send("我喜欢你"); // 测试向主isolate发送消息
receivePort.listen((message) {
print("接到函数main()消息::" + message);
});
}
Isolate.run()
- 由于使用ReceivePort太过繁杂,因此dart封装了Isolate.run()函数,其内部自己生成ReceivePort并用Isolate.spawn()传入,简化了开发者的使用。
- 注意:该方法需要在 Dart 2.19 以上的版本使用,对应 Flutter 3.7.0 以上。
- 具体使用如下:
void main() async {
// Read some data.
final jsonData = await Isolate.run(() async {
final fileData = await File(filename).readAsString();
final jsonData = jsonDecode(fileData) as Map<String, dynamic>;
return jsonData;
});
// Use that data.
print('Number of JSON keys: ${jsonData.length}');
}
Isolate.compute()
- 除了上述在 Dart 中的用法外,我们还可以在 Flutter 中通过
compute()来实现。并且这也是官方推荐的用法,因为compute()允许在非原生平台 Web 上运行。
使用场景以及缺点
- 一般情况下,常用的还是Future处理就好,但当时间处理超过16ms以上,就可以使用isolate.
PS:屏幕一帧的刷新间隔就是 16ms
常用场景
- JSON解析: 解码JSON,这是HttpRequest的结果,可能需要一些时间
- 加解密: 加解密过程比较耗时
- 图片处理: 比如裁剪图片比较耗时
- 从网络中加载大图
缺点:
- isolate 消耗较重,除了创建耗时,每次创建还至少需要2Mb的空间,有OOM的风险。
- isolate 之间的内存空间各自独立,当参数或结果跨 iso 相互传递时需要深度拷贝,拷贝耗时,可能造成UI卡顿。
OOM:out of memory,内存泄漏和内存溢出
dart2.5新特性,性能提升(能有效解决缺点2)
当一个 isolate 调用了 Isolate.spawn(),两个 isolate 将拥有同样的执行代码,并归入同一个 isolate 组 中。 Isolate 组会带来性能优化,例如新的 isolate 会运行由 isolate 组持有的代码,即共享代码调用。同时,Isolate.exit() 仅在对应的 isolate 属于同一组时有效。
总结:使用 exit() 替代 SendPort.send,可规避数据复制,节省耗时。
参考资料: