前言
什么是Isolate呢?答:Dart中提供的独立并发执行单元。发现了吗?没错,它就是弥补Dart单线程无法直接并行执行任务的短板。我们都知道Dart的单线程模型非常完美,但当遇到密集计算时仍然会阻塞UI,那怎么解决这个问题呢?Dart中的Isolate给出了答案,它能够将耗时的任务转移到独立线程中去执行,从而保持主线程的流畅。那Isolate都是怎么实现的呢?我们一起去看看吧。
一、为什么需要Isolate?
因为Isolate能够解决如下几个问题:
- 弥补单线程的局限: Dart单线程模型虽然能够通过事件循环+任务队列实现并发,但其所有任务还是执行在同一个线程上的,所以当遇到耗时的密集计算任务时仍然会阻塞UI。Isolate能够开辟出一个新的线程进行耗时计算,保持了主线程的流畅。
- 充分利用资源: 现代设备多为多核CPU,但Dart是单线程,只能使用一核CPU,无法并行加速。可创建多个Isolate,将每个Isolate分配到不同CPU核上运行,实现真正的并行计算。
- 规避共享内存的复杂性: Isolate之间是完全隔离的,通过消息传递进行通信,天然避免了共享状态的问题。
二、Isolate的定义
Isolate是Dart中提供的独立并发执行单元。下面我们通过问题的方式一起去理解这个定义。
-
为什么说它是独立的呢?
答:因为每个Isolate都拥有属于自己的内存堆,彼此之间不共享任何可变数据。也就是说Isolate之间是内存隔离的(所以天然避免了共享状态的问题)。简单理解就是在不同的Isolate中的创建的同名变量不会产生冲突。 -
既然Isolate是独立的,那多个Isolate之间是如何通信的呢?
答:Isolate之间通过消息传递通信,通过SendPort和ReceivePort建立通信通道。消息传递采用拷贝机制或引用转移。注意:传递的必须是可序列化的。
三、Isolate的创建
Isolate的创建方式有三种形式,分别是spawn()方法基础创建、run()方法简化一次性任务的执行,自动管理Isolate生命周期、顶级函数compute()简化Isolate。
3.1、Isolate.spawn()创建
Isolate.spawn()是最基础的创建方法,其创建需要手动管理消息传递,适用于需要精细控制Isolate生命周期的场景。由于其需要手动管理消息传递,下面我们先介绍建立通信通道的SendPort、ReceivePort。
ReceivePort: 消息接收端。其是一个消息监听器,用于接收来自其他 Isolate 的消息。
每个 ReceivePort 对应一个唯一的 SendPort(通过 sendPort 属性获取),用于向该端口发送消息。
从图中可以看出,ReceivePort是对Stream的实现,因此ReceivePort的监听和Stream的监听一致,使用listen()监听,本小节不再赘述。
SendPort: 消息发送端。其是一个消息发送器,用于向其他 Isolate 的 ReceivePort 发送数据。
如何创建? Isolate.spawn()如何创建的,需要先了解spawn()方法有哪些参数。
- 必传参数:
- entryPoint:Isolate的入口函数,必须为静态方法或顶层函数,传入参数类型必须与message参数一致。
- message:传递给entryPoin的参数,其必须是可序列化的(如基本类型、SendPort、List、Map等)。
- 可选参数:
- paused:为true时,创建的Isolate会启动后暂停,若想要重新启动需要调用Isolate.resume()。
- errorsAreFatal:为true时,未捕获的异常会导致Isolate终止。
- onExit:退出创建的Isolate时,向SendPort 发送null或exitCode。
- onError:未捕获异常时,向SendPort发送错误信息。
- debugName:为创建的Isolate取个名用于标识。
示例: 启动新Isolate并建立通信。
void buildIsolate() async{
final receivePort = ReceivePort();
// entryPoint参数 为需要传入参数为SendPort对象的函数。
// message参数为通过ReceivePort获取的SendPort。
final isolate = await Isolate.spawn((sendPort){sendPort.send('hello,Dart!');},receivePort.sendPort);
// 通过listen()监听听结果
receivePort.listen((data){print('接收到的信息为:$data');});
}
3.2、Isolate.run()
用于简化一次性任务的执行,自动管理Isolate的生命周期。核心是对Isolate.spawn()的进一步封装。 其参数如下图所示:
- computation:为执行同步任务或异步任务的函数。
- debugName:用于标识Isolate的名字。
示例: 执行同步耗时计算。
void buildIsolateWithRun() async {
final result = await Isolate.run((){
int sum = 0;
for (int i=0; i<100000000;i++){
sum += i;
}
return sum;
});
}
示例: 执行异步网络请求。
void buildIsolateWithRun1() async {
final result = await Isolate.run(() async {
Dio dio = Dio();
final response = await dio.get('https://api.example.com/data');
return response;
});
}
3.3、compute()
顶层函数compute()是 Dart 提供的一个便捷函数,专门用于简化一次性Isolate任务的执行。它是对 Isolate.spawn()的高层封装(在Isolate.run()的基础上封装),自动管理 Isolate 的生命周期、消息传递和资源释放。其参数如下图所示:
- callback:在新启的Isolate中执行的函数,必须为顶层函数或静态方法。
- message:传递给callback的参数,必须是可序列化的。
- debugLabel:调试标签。
从下图看出,顶层函数compute()底层为Isolate.run()。
示例:
void buildIsolateWithCompute() async {
// 传入参数message必须为可序列化的。
final result = await compute(sendMessage, [1,2,3]);
print(result);
}
int sendMessage(List<int> _list){
// 执行耗时计算任务
return _list[0];
}
四、Isolate的属性
- Isolate.current:获取当前Isolate实例。
- Isolate.packageConfig:获取当前Isolate的包配置。
- isolate.pauseCapability:暂停的权限令牌。
注意:本小节中isolate为Isolate的实例,下同。
示例:
void buildIsolate() async{
final receivePort = ReceivePort();
Isolate isolate = await Isolate.spawn((sendPort){sendPort.send('hello,Dart!');},receivePort.sendPort);
// 获取当前Isolate实例
Isolate currentIsolate = Isolate.current;
// 获取包配置
print(Isolate.packageConfig);
// 当前Isolate的暂停权限令牌。
Capability? pauseToken = isolate.pauseCapability;
}
五、Isolate的暂停、恢复、终止
- isolate.pause():暂停Isolate。是一个异步方法,使用时需使用await等待。
- isolate.resume():恢复 Isolate,需要传入权限令牌。
- isolate.kill():终止指定的 Isolate 实例。
- Isolate.exit():静态方法,终止当前正在运行的 Isolate。
示例:
void buildIsolate() async{
final receivePort = ReceivePort();
Isolate isolate = await Isolate.spawn((sendPort){sendPort.send('hello,Dart!');},receivePort.sendPort);
// 1.暂停Isolate
await isolate.pause();
Capability? pauseToken = isolate.pauseCapability;
if(pauseToken != null){
// 2.恢复Isolate
isolate.resume(pauseToken);
}
// 3.终止Isolate
isolate.kill();
// Isolate.exit();
}
六、总结
本小节从为什么需要Isolate的问题出发,首先介绍了Isolate的不可或缺性,其次介绍了Isolate创建的三种方式,然后在介绍了Isolate属性的基础上介绍了如何暂停、终止、恢复Isolate。下面是本小节的归纳总结:
| Isolate的创建 | Isolate的属性 | Isolate的暂停、恢复、终止 |
|---|---|---|
| 1、Isolate.spawn() 2、Isolate.run() 3、compute() | 1、Isolate.current 2、Isolate.packageConfig 3、isolate.pauseCapability | 1、isolate.pause() 2、isolate.resume() 3、isolate.kill() 4、Isolate.exit() |