Dart 并发与 Isolate
Dart 并发与 Isolate 并发是指同时处理多个任务的能力。 Dart 的并发模型不同于传统的多线程,它使用 Isolate(隔离区)来实现并行计算。 本章介绍 Dart 的并发模型、Isolate 的概念与使用以及消息传递机制。 Dart 并发模型 大多数编程语言使用共享内存的多线程模型,多个线程共享同一个内存空间。 这种模型的缺点是容易产生竞态条件(race condition)和死锁(deadlock)。 Dart 采..
教程详情页默认 noindex。
Dart 并发与 Isolate
并发是指同时处理多个任务的能力。
Dart 的并发模型不同于传统的多线程,它使用 Isolate(隔离区)来实现并行计算。
本章介绍 Dart 的并发模型、Isolate 的概念与使用以及消息传递机制。
Dart 并发模型
大多数编程语言使用共享内存的多线程模型,多个线程共享同一个内存空间。
这种模型的缺点是容易产生竞态条件(race condition)和死锁(deadlock)。
Dart 采用了不同的策略:每个 Isolate 拥有自己独立的内存堆,Isolate 之间不共享内存。
它们通过消息传递来通信,这从根本上避免了数据竞争问题。
Dart 的并发模型有三个层次:
| 层次 | 机制 | 适用场景 |
|---|---|---|
| 事件循环 | 单线程异步(Event Loop) | I/O 操作、定时器、用户交互 |
| Isolate | 独立内存 + 消息传递 | CPU 密集型计算 |
| Future/Stream | 异步编程语法 | 大多数日常开发场景 |
大多数 Dart 程序的并发需求都可以通过 async/await 和 Future/Stream 来满足。Isolate 只在你需要进行大量 CPU 密集型计算时才有必要使用。过早引入 Isolate 会让代码变复杂,收益却不大。
Isolate 概念与使用
Isolate 是 Dart 的并发单元,每个 Isolate 有自己独立的内存和事件循环。
主程序本身就在一个 Isolate(主 Isolate)中运行。
使用 Isolate.spawn 创建新 Isolate
实例
// 这个函数会在新的 Isolate 中运行
// SendPort 用于向主 Isolate 发送消息
void heavyComputation(SendPort sendPort) {
print('新 Isolate 开始计算...');
// 模拟 CPU 密集型计算
int sum = 0;
for (int i = 1; i <= 10000000; i++) {
sum += i;
}
// 将计算结果发送回主 Isolate
sendPort.send(sum);
print('新 Isolate 计算完成,结果已发送');
}
Future<void> main() async {
print('主 Isolate 启动');
// 创建一个 ReceivePort 用于接收消息
var receivePort = ReceivePort();
// spawn 创建新的 Isolate
await Isolate.spawn(heavyComputation, receivePort.sendPort);
print('主 Isolate 在等待结果的同时可以做其他事情...');
// 等待新 Isolate 的计算结果
var result = await receivePort.first;
print('RUNOOB 计算结果: 1 到 10000000 的和 = $result');
receivePort.close();
print('主 Isolate 结束');
}
主 Isolate 启动 主 Isolate 在等待结果的同时可以做其他事情... 新 Isolate 开始计算... 新 Isolate 计算完成,结果已发送 RUNOOB 计算结果: 1 到 10000000 的和 = 50000005000000 主 Isolate 结束
对比:有无 Isolate 的性能差异
实例
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
void main() {
// 在主 Isolate 中计算——会阻塞所有其他操作
var startTime = DateTime.now();
// 注意:如果 n 太大,这个计算会非常耗时
// 实际开发中应该把这种计算放到单独的 Isolate 中
var result = fibonacci(40);
var elapsed = DateTime.now().difference(startTime);
print('RUNOOB 斐波那契(40) = $result');
print('耗时: ${elapsed.inMilliseconds}ms');
print('(注意:在计算期间,主 Isolate 无法处理其他任务)');
}
在 Flutter 应用中,如果在主 Isolate 中执行耗时的同步计算,会导致 UI 冻结。解决方案就是将计算任务放到单独的 Isolate 中,计算完成后通过消息将结果传回并更新 UI。
消息传递机制
Isolate 之间通过 SendPort 和 ReceivePort 进行消息传递。
消息必须是可序列化的(基本类型、String、List、Map 等),不能传递函数或闭包。
双向通信
实例
// 在新 Isolate 中运行的工作函数
void workerIsolate(SendPort mainSendPort) {
// 创建自己的 ReceivePort 来接收主 Isolate 的消息
var workerReceivePort = ReceivePort();
// 先把 worker 的 SendPort 发给主 Isolate,建立双向通道
mainSendPort.send(workerReceivePort.sendPort);
print('Worker Isolate: 等待任务...');
// 监听主 Isolate 发来的任务
workerReceivePort.listen((message) {
if (message is List<int>) {
// 收到任务:对列表中的每个数求平方
print('Worker Isolate: 收到数据 $message');
var result = message.map((n) => n * n).toList();
// 通过 mainSendPort 发回结果
mainSendPort.send(result);
} else if (message == 'exit') {
print('Worker Isolate: 收到退出信号,关闭');
workerReceivePort.close();
mainSendPort.send('goodbye');
}
});
}
Future<void> main() async {
print('主 Isolate: 启动');
// 创建主接收端口
var mainReceivePort = ReceivePort();
// 启动 Worker Isolate
await Isolate.spawn(workerIsolate, mainReceivePort.sendPort);
// 等待 Worker 发来它的 SendPort(建立双向通信)
SendPort? workerSendPort;
await for (var msg in mainReceivePort) {
if (msg is SendPort) {
workerSendPort = msg;
print('主 Isolate: 已建立与 Worker 的双向通信');
break;
}
}
// 通过 Worker 的 SendPort 发送任务
workerSendPort!.send([1, 2, 3, 4, 5]);
workerSendPort.send([10, 20, 30]);
// 接收 Worker 的计算结果
int responseCount = 0;
await for (var msg in mainReceivePort) {
if (msg is List<int>) {
print('主 Isolate: 收到结果 $msg');
responseCount++;
if (responseCount == 2) break;
}
}
// 发送退出信号
workerSendPort.send('exit');
await for (var msg in mainReceivePort) {
if (msg == 'goodbye') {
print('主 Isolate: Worker 已退出');
break;
}
}
mainReceivePort.close();
print('RUNOOB 双向通信演示结束');
}
主 Isolate: 启动 主 Isolate: 已建立与 Worker 的双向通信 Worker Isolate: 等待任务... Worker Isolate: 收到数据 [1, 2, 3, 4, 5] 主 Isolate: 收到结果 [1, 4, 9, 16, 25] Worker Isolate: 收到数据 [10, 20, 30] 主 Isolate: 收到结果 [100, 400, 900] Worker Isolate: 收到退出信号,关闭 主 Isolate: Worker 已退出 RUNOOB 双向通信演示结束
使用 Isolate.run 简化(Dart 3.0+)
Dart 3.0 引入了 Isolate.run(),大大简化了单次计算任务的使用。
实例
// 一个耗时的计算函数
int complexCalculation(int n) {
int result = 0;
for (int i = 0; i < n; i++) {
result += i * i;
}
return result;
}
Future<void> main() async {
print('开始计算...');
// Isolate.run:一行代码搞定,自动创建 Isolate、执行、返回结果
var result = await Isolate.run(() => complexCalculation(10000000));
print('RUNOOB 计算结果: $result');
print('计算完成');
// 对比:如果不使用 Isolate
print('(如果在主 Isolate 中直接计算,会阻塞其他操作)');
}
开始计算... RUNOOB 计算结果: 333333283333335000000 计算完成 (如果在主 Isolate 中直接计算,会阻塞其他操作)
Isolate.run() 适合"执行一次计算并返回结果"的场景。如果你需要持续的、双向的通信(比如长时间运行的后台服务),则需要使用 Isolate.spawn() + SendPort/ReceivePort。
Isolate 消息传递的限制
| 可以传递 | 不能传递 |
|---|---|
| null、bool、int、double、String | 函数、闭包 |
| List、Map、Set(元素也可传递) | Stream、Future |
| SendPort(用于建立通信链) | 大多数非基本类型的对象 |
| Capability(权限令牌) | 文件句柄、网络 Socket |
| TransferableTypedData(高效传递大块数据) | 自定义类(除非可序列化) |
并发模型对比
| 特性 | Dart Isolate | 传统多线程(如 Java) |
|---|---|---|
| 内存模型 | 独立内存,不共享 | 共享内存 |
| 通信方式 | 消息传递(SendPort) | 共享变量 + 锁 |
| 数据竞争 | 不存在(无共享) | 需要锁机制保护 |
| 死锁风险 | 几乎不存在 | 存在 |
| 创建开销 | 相对较大 | 相对较小 |
| 适用场景 | CPU 密集型并行计算 | 通用并发 |
Dart 的 Isolate 模型虽然避免了数据竞争,但代价是创建和通信的开销较大。不要创建成千上万个 Isolate——通常几个 Isolate 就足够了。对于 I/O 密集型任务,使用 async/await 是最佳选择。