Dart 之Isolate

441 阅读6分钟

Dart之Isolate.png

前言

什么是Isolate呢?答:Dart中提供的独立并发执行单元。发现了吗?没错,它就是弥补Dart单线程无法直接并行执行任务的短板。我们都知道Dart的单线程模型非常完美,但当遇到密集计算时仍然会阻塞UI,那怎么解决这个问题呢?Dart中的Isolate给出了答案,它能够将耗时的任务转移到独立线程中去执行,从而保持主线程的流畅。那Isolate都是怎么实现的呢?我们一起去看看吧。

一、为什么需要Isolate?

因为Isolate能够解决如下几个问题:

  • 弥补单线程的局限: Dart单线程模型虽然能够通过事件循环+任务队列实现并发,但其所有任务还是执行在同一个线程上的,所以当遇到耗时的密集计算任务时仍然会阻塞UI。Isolate能够开辟出一个新的线程进行耗时计算,保持了主线程的流畅。
  • 充分利用资源: 现代设备多为多核CPU,但Dart是单线程,只能使用一核CPU,无法并行加速。可创建多个Isolate,将每个Isolate分配到不同CPU核上运行,实现真正的并行计算。
  • 规避共享内存的复杂性: Isolate之间是完全隔离的,通过消息传递进行通信,天然避免了共享状态的问题。

二、Isolate的定义

Isolate是Dart中提供的独立并发执行单元。下面我们通过问题的方式一起去理解这个定义。

  1. 为什么说它是独立的呢?
    答:因为每个Isolate都拥有属于自己的内存堆,彼此之间不共享任何可变数据。也就是说Isolate之间是内存隔离的(所以天然避免了共享状态的问题)。简单理解就是在不同的Isolate中的创建的同名变量不会产生冲突。

  2. 既然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 属性获取),用于向该端口发送消息。 image.png 从图中可以看出,ReceivePort是对Stream的实现,因此ReceivePort的监听和Stream的监听一致,使用listen()监听,本小节不再赘述。

SendPort: 消息发送端。其是一个消息发送器,用于向其他 Isolate 的 ReceivePort 发送数据。

如何创建? Isolate.spawn()如何创建的,需要先了解spawn()方法有哪些参数。

image.png

  1. 必传参数:
  • entryPoint:Isolate的入口函数,必须为静态方法或顶层函数,传入参数类型必须与message参数一致。
  • message:传递给entryPoin的参数,其必须是可序列化的(如基本类型、SendPort、List、Map等)。
  1. 可选参数:
  • 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的名字。

image.png

示例: 执行同步耗时计算。

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:调试标签。

image.png

从下图看出,顶层函数compute()底层为Isolate.run()。

image.png

示例:

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()