Dart 中的 Isolate

在 Dart 中,Isolate 是一种用于并发处理的机制。它与线程类似,但与传统线程有所不同。每个 Isolate 都有自己的内存空间和事件循环,彼此之间无法直接共享内存,(有点多进程的概率)因此它们之间的通信需要通过消息传递(SendPortReceivePort)来实现。

下面是 Dart 中 Isolate 使用的一些基本示例,包括创建 Isolate、在多个 Isolate 之间传递消息以及如何管理它们的生命周期。

一、什么是事件循环

在 Dart 的线程中也存在事件循环和消息队列的概念,但在 Dart 中线程叫做isolate。应用程序启动后,开始执行 main 函数并运行 main isolate

每个 isolate 包含一个事件循环以及两个事件队列,event loop事件循环,以及event queuemicrotask queue事件队列

  • event queue:负责处理I/O事件、绘制事件、手势事件、接收其他 isolate 消息等外部事件。
  • microtask queue:可以自己向 isolate 内部添加事件,事件的优先级比 event queue高。不建议使用

二、 创建基本的 Isolate

1. 使用 Isolate.spawn 创建新的 Isolate

Isolate.spawn 是最常见的创建 Isolate 的方式,它用于启动一个新的 Isolate 并指定其执行的函数。这个方法接受一个执行函数和一个函数参数,执行函数将在新的 Isolate 中执行。

示例:创建一个新的 Isolate
import 'dart:isolate';

void sayHello(String message) {
    
    
  print('Hello from Isolate: $message');
}

void main() async {
    
    
  // 创建一个新的 Isolate,并将任务传递给它
  await Isolate.spawn(sayHello, "ok");
  print('main Isolate is running');
}

输出:

hello from isolate:ok
main Isolate is running

在这个示例中,Isolate.spawn 方法创建了一个新的 Isolate,并让它执行 sayHello 函数,传递一个参数 "ok"

  • Isolate.spawn 需要传递两个参数:一个回调函数(即将要在新 Isolate 中执行的函数)和参数(将传递给该函数)。
  • Isolate.spawn 会在主 Isolate 中返回一个 Future,在新的 Isolate 完成执行后,Future 会被完成。

2. 使用 Isolate.spawnUri 从外部文件启动 Isolate

Isolate.spawnUri 允许从外部 Dart 文件中加载并执行一个新的 Isolate。它的作用类似于 Isolate.spawn,但是可以从指定的文件中加载要执行的代码。

//main.dart
import 'dart:isolate';

void main() async {
    
    
  // 创建一个 ReceivePort,用来接收 worker.dart 的消息
  final receivePort = ReceivePort();

  // 启动一个新的 Isolate,传递 worker.dart 的 URI 和 ReceivePort 的 SendPort
  final uri = Uri.parse('worker.dart');  // worker.dart 文件的 URI
  await Isolate.spawnUri(uri, ['Hello', 'from', 'main'], receivePort.sendPort);

  // 监听从 worker.dart 发来的消息
  receivePort.listen((message) {
    
    
    print('Received from worker: $message');
    receivePort.close();  // 接收到消息后关闭 ReceivePort
  });

  print('Main Isolate is running...');
}

//worker.dart
import 'dart:isolate';

void main(List<String> args) {
    
    
  // 打印收到的命令行参数
  print('Worker received arguments: ${args.join(' ')}');

  // 创建一个 ReceivePort,用于接收消息
  final receivePort = ReceivePort();

  // 向主线程发送一条消息
  receivePort.sendPort.send('Hello from worker!');

  // 监听主线程的消息(如果有的话)
  receivePort.listen((message) {
    
    
    print('Received from main: $message');
  });

  // 模拟一些耗时的工作
  Future.delayed(Duration(seconds: 1), () {
    
    
    receivePort.sendPort.send('Worker done!');
  });
}

打印结果:

Main Isolate is running...
Worker received arguments: Hello from main
Received from worker: Hello from worker!
Received from main: Worker done!

进一步说明

  • 命令行参数:在 worker.dart 中,args 参数是传递给 main 函数的列表。在本例中,args 包含了从 main.dart 传递过来的字符串数组 ['Hello', 'from', 'main']
  • 消息传递:主线程通过 ReceivePort 接收来自子线程(worker.dart)的消息。子线程通过其 SendPort 向主线程发送消息。
  • Isolate 独立性Isolate.spawnUri 启动的新 Isolate 是完全独立的,与主线程的内存和上下文隔离,它们之间的通信只能通过消息传递(即 SendPortReceivePort)来实现
  • 主线程将自己的 ReceivePortsendPort 传递给新的 Isolate。这个 SendPort 的作用是让主线程能够向新创建的 Isolate 发送消息。
  • worker.dart 中必须要有自己的 main 函数

三、 通过 SendPort 和 ReceivePort 进行通信

由于每个 Isolate 拥有独立的内存空间,它们之间不能直接共享数据。因此,Dart 提供了 SendPortReceivePort 来进行进程间通信。

示例:在 Isolate 之间传递消息
import 'dart:isolate';

void isolateEntry(SendPort sendPort) {
    
    
  // 向主 Isolate 发送消息
  sendPort.send('Hello from Isolate!');
}

void main() async {
    
    
  // 创建 ReceivePort,主 Isolate 用来接收消息
  final receivePort = ReceivePort();
  
  // 创建新的 Isolate,并传递 SendPort
  await Isolate.spawn(isolateEntry, receivePort.sendPort);
  
  // 从 ReceivePort 获取消息
  receivePort.listen((message) {
    
    
    print('Received message: $message');
    receivePort.close(); // 关闭 ReceivePort
  });

  print('Main Isolate is running.');
}

输出:

Isolate is running.
Received message: Hello from Isolate!

在这个示例中:

  • 主 Isolate 创建了一个 ReceivePort 来接收消息。
  • 新的 Isolate 通过 sendPort.send 向主 Isolate 发送消息。
  • 主 Isolate 通过 receivePort.listen 监听来自 Isolate 的消息,并在收到消息时打印它。

四、 处理复杂数据类型

Isolate 之间传递的消息只能是可传递的消息(比如基本数据类型、ListMap 等),而且它们会被复制到新的 Isolate 中。因此,传递复杂的对象(比如自定义类实例)时,需要进行序列化/反序列化。

1、发送一个复杂数据

示例:传递一个 Map 数据

import 'dart:isolate';

void processData(SendPort sendPort) {
    
    
  // 发送一个包含多个字段的 Map
  sendPort.send({
    
    'name': 'Alice', 'age': 30});
}

void main() async {
    
    
  final receivePort = ReceivePort();

  await Isolate.spawn(processData, receivePort.sendPort);

  // 监听接收到的消息
  receivePort.listen((message) {
    
    
    print('Received data: $message');
    receivePort.close();
  });

  print('Main Isolate is running.');
}

输出:

Isolate is running.
Received data: {name: Alice, age: 30}

2、传递一个复杂数据

示例:传递一个 Map 数据

import 'dart:isolate';

void sendMessage2(Map map){
    
    
  map['port'].send("来自 isolate 中的消息!");
}

void main() async {
    
    
  final receivePort = ReceivePort();
  final map = {
    
    'name':'张三','port':receivePort.sendPort};
  await Isolate.spawn(sendMessage2, map);
  
  receivePort.listen((message){
    
    
    print('收到消息:$message');
    receivePort.close();
  });
  
  print('main Isolate is running');
}

输出:

hello from isolate:ok
main Isolate is running
收到消息:来自 isolate 中的消息!

五、并发执行多个 Isolate

可以同时启动多个 Isolate,使得它们并行处理不同的任务。以下是一个示例,展示如何创建多个 Isolate 并执行不同的任务。

示例:启动多个 Isolate 进行并发计算
import 'dart:isolate';

void computeTask(Map map) {
    
    
  int result = map['index'] * 2;
  map['port'].send('Task result: $result');
}

void main() async {
    
    
  final receivePort = ReceivePort();
  
  // 启动多个 Isolate 来执行并行任务
  for (int i = 1; i <= 5; i++) {
    
    
    final map = {
    
    'index': i, 'port': receivePort.sendPort};
    await Isolate.spawn(computeTask, map);
  }

  // 监听来自各个 Isolate 的结果
  receivePort.listen((message) {
    
    
    print(message);
  });

  print('Main Isolate is running.');
}

输出:

Isolate is running.
Task result: 2
Task result: 4
Task result: 6
Task result: 8
Task result: 10

六、 捕获异常

Isolate 的执行过程中可能会抛出异常,你可以使用 Isolate.spawnonError 参数来捕获这些异常。

示例:捕获异常
import 'dart:isolate';

void errorProneTask(SendPort sendPort) {
    
    
  throw Exception('An error occurred in the Isolate');
}

void main() async {
    
    
  final receivePort = ReceivePort();

  // 传递错误处理的 ReceivePort
  await Isolate.spawn(errorProneTask, receivePort.sendPort, onError: receivePort.sendPort);

  // 监听错误信息
  receivePort.listen((message) {
    
    
    if (message is String && message.startsWith('Exception')) {
    
    
      print('Caught error: $message');
    } else {
    
    
      print('Received message: $message');
    }
  });

  print('Main Isolate is running.');
}

输出:

Isolate is running.
Caught error: Exception: An error occurred in the Isolate

七、在 Isolate 中使用 asyncawait

Isolate 的工作线程本身也可以使用 Dart 的异步机制。你可以在 Isolate 中执行异步操作,如网络请求等。

示例:在 Isolate 中使用异步操作
import 'dart:isolate';
import 'dart:async';

Future<void> asyncTask(SendPort sendPort) async {
    
    
  await Future.delayed(Duration(seconds: 2));
  sendPort.send('Task completed');
}

void main() async {
    
    
  final receivePort = ReceivePort();

  // 启动一个 Isolate 执行异步任务
  await Isolate.spawn(asyncTask, receivePort.sendPort);

  // 监听异步任务结果
  receivePort.listen((message) {
    
    
    print(message); // 输出 "Task completed"
    receivePort.close(); // 关闭 ReceivePort
  });

  print('Main Isolate is running.');
}

输出:

Isolate is running.
Task completed

总结

通过上述示例,你可以看到 Dart 中的 Isolate 如何在并行计算和并发处理方面发挥作用。每个 Isolate 都是一个独立的执行单元,它拥有自己的内存和事件循环,因此不能直接共享数据。相反,数据交换需要通过 SendPortReceivePort 进行。Isolate 是一种高效的并发机制,适用于计算密集型任务,避免了线程安全问题并有效利用多核处理器。

参考连接:https://juejin.cn/post/7039115158261596191