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 是最佳选择。
