文章目录
Dart是不是单线程模型?是如何运行的?
一句话总结
- Dart 是单线程模型,它的核心是事件循环机制 ,通过微任务队列 和事件队列来高效地处理异步操作,从而保证了UI线程的流畅性。
核心概念
Dart是单线程模型,不必像在Java或C#中那样担心共享内存的锁竞争和线程同步问题。运行机制可以从以下三个层面来理解:
1. 核心机制:事件循环 (Event Loop)
Dart 的事件循环依赖两个有序队列,按优先级执行:
- 微任务队列 (Microtask Queue) :优先级最高;通过
scheduleMicrotask()
显式添加,附着在这个 Future 上的回调(包括then
、catchError
、whenComplete
以及await
的续延代码)都会被调度到微任务队列中执行。 - 事件队列 (Event Queue) :我们日常使用的绝大部分异步操作都来源于这里,例如
I/O操作
(文件、网络请求)、手势识别
、绘图消息
、Timer
(Future.delayed
)。
事件循环的工作流程图:
flowchart TD
A[事件循环启动] --> B{微任务队列
是否为空?} B -- 否 --> C[取出并执行
一个微任务] C --> B B -- 是 --> D{事件队列
是否为空?} D -- 否 --> E[取出并执行
一个事件] E --> F[事件执行完毕] F --> B D -- 是 --> G[等待新事件到来] G --> B
是否为空?} B -- 否 --> C[取出并执行
一个微任务] C --> B B -- 是 --> D{事件队列
是否为空?} D -- 否 --> E[取出并执行
一个事件] E --> F[事件执行完毕] F --> B D -- 是 --> G[等待新事件到来] G --> B
事件循环代码演示:
dart
void main() {
// ------------------------------
// 1. 同步代码(优先执行,事件循环启动前)
// ------------------------------
print("1. 同步代码 - 开始执行");
// ------------------------------
// 2. 微任务队列(Microtask Queue):优先级高于事件队列
// 通过 Future.microtask 添加
// ------------------------------
Future.microtask(() {
print("2. 微任务A - 微任务队列");
});
// ------------------------------
// 3. 事件队列(Event Queue):优先级低于微任务队列
// 通过 Future 或 Future.delayed 添加(即使延迟0秒)
// ------------------------------
// 事件1:执行时会新增微任务
Future(() {
print("4. 事件1 - 事件队列(开始执行)");
// 在事件执行过程中新增微任务
Future.microtask(() {
print("5. 微任务B(事件1中新增) - 微任务队列");
});
print("6. 事件1 - 事件队列(执行结束)");
});
// 再添加一个微任务(微任务队列按添加顺序执行)
Future.microtask(() {
print("3. 微任务C - 微任务队列");
});
// 事件2:在事件1之后执行
Future.delayed(Duration.zero, () {
print("7. 事件2 - 事件队列");
});
// ------------------------------
// 1. 同步代码(继续执行)
// ------------------------------
print("8. 同步代码 - 执行结束(事件循环即将启动)");
}
执行结果:
plaintext
1. 同步代码 - 开始执行
8. 同步代码 - 执行结束(事件循环即将启动)
2. 微任务A - 微任务队列
3. 微任务C - 微任务队列
4. 事件1 - 事件队列(开始执行)
6. 事件1 - 事件队列(执行结束)
5. 微任务B(事件1中新增) - 微任务队列
7. 事件2 - 事件队列
步骤拆解(对应执行结果):
- 同步代码优先执行 :
打印1
和8
(同步代码不进入任何队列,事件循环启动前就执行完毕)。 - 事件循环启动,先清空微任务队列 :
按添加顺序执行微任务 A 和 C,打印2
和3
(微任务队列此时为空)。 - 处理事件队列的第一个事件(事件 1) :
- 执行事件 1 的代码,打印
4
和6
。 - 事件 1 执行过程中新增微任务 B(加入微任务队列)。
- 执行事件 1 的代码,打印
- 事件 1 执行完毕后,再次清空微任务队列 :
执行新增的微任务 B,打印5
(微任务队列再次为空)。 - 处理事件队列的下一个事件(事件 2) :
执行事件 2,打印7
(事件队列此时为空)。 - 所有队列清空,程序结束。
2. 实现异步的关键:Future 和 async/await
- Future :它不是一个并行计算的结果,而是一个 "承诺" ,承诺在未来某个事件循环轮次中给你一个值(或一个错误)。当你调用
http.get()
或Future.delayed()
时,Dart会立刻返回一个Future对象,并立即返回(不会阻塞),I/O操作(网络/文件)由操作系统异步处理(Dart线程之外),主线程(Dart线程)仅处理回调。 - async/await :这只是编写异步代码的 "语法糖" ,让异步代码看起来和同步代码一样直观。在函数前加上
async
关键字,其返回值会被自动包装为Future
。await
关键字的作用是:告诉事件循环"我这边有个Future需要一些时间才能完成,你先去处理其它事件(微任务或UI事件),等这个Future有结果了再回来继续执行我后面的代码。" 这非常重要,它不会阻塞线程,而是让出执行权。
代码演示:
dart
// 需安装http库 flutter pub add http
import 'package:http/http.dart' as http;
void loadData() async {
print('1: 开始请求');
var data = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
); // 不会阻塞,只是"等待"
print('3: 收到数据: $data');
}
void main() {
loadData();
print('2: 执行其它操作');
}
执行结果:
plaintext
1: 开始请求
2: 执行其它操作
3: 收到数据: Instance of 'Response'
梳理流程:
-
发起请求(同步) :
http.get()
被调用。- 它同步地创建一个
Future
对象并返回。 - 它同步地发起一个系统调用(比如用
libcurl
或系统的 HTTP 库),将这个网络 I/O 操作委托给操作系统在 Dart 线程之外 执行。此时,Dart 线程本身是空闲的,可以继续执行事件循环。
-
遇到
await
(同步 -> 异步) :await
关键字检查它右边的Future
。如果这个 Future 尚未完成(http.get
返回的肯定未完成),它会暂停当前函数的执行。- 它向这个未完成的 Future 订阅一个回调 。这个回调包含了
await
之后的所有代码(即print('3: ...')
)。 - 这个回调的调度机制,与
future.then(...)
的回调调度机制是完全相同的。
-
等待与完成(在Dart线程外) :
- Dart 事件循环继续运行,处理微任务和UI事件(比如执行
print('2')
)。 - 操作系统在后台处理网络请求。
- Dart 事件循环继续运行,处理微任务和UI事件(比如执行
-
请求完成,通知Dart(事件队列) :
- 当操作系统完成网络请求后,它会通过一种机制(如
epoll
、kqueue
或IOCP
)通知 Dart 运行时。 - 这个"通知"作为一个事件,被放入 Dart 的
事件队列(Event Queue)
中。 这个事件本身不包含数据处理逻辑,只是一个信号。
- 当操作系统完成网络请求后,它会通过一种机制(如
-
事件循环处理完成事件(从事件队列到微任务队列) :
- 事件循环从事件队列中取出这个"网络请求完成"的事件。
- 处理这个事件的代码(Dart 运行时内部代码)会完成 之前
http.get
返回的那个 Future 对象(即调用_completer.complete(response)
)。 - 最关键的一步来了:当
future.complete()
被调用时,Dart 的运行时会 schedule 一个微任务。这个微任务的任务就是:执行所有通过then
、catchError
或await
注册在这个 Future 上的回调函数。
-
执行回调(微任务队列) :
- 当事件循环处理完当前的事件后,它会检查微任务队列。
- 它发现了第5步中被调度的微任务,于是执行它。
- 这个微任务的内容就是执行
print('3: 收到数据: $data')
。
操作系统完成I/O -> 发送信号至 Dart 事件队列
-> 事件循环处理该信号 -> Future.complete()
被调用 -> 调度一个微任务
来执行所有回调 -> 事件循环处理微任务
-> 执行 await
之后的代码。
3. 处理真正耗时计算:Isolate
事件循环和异步处理对于I/O操作是完美的,但对于CPU密集型计算 (如图像处理、加密解密、复杂JSON解析),如果计算本身就在Dart线程上运行,它就会阻塞事件循环,导致队列中的所有其他任务(包括UI渲染)都无法处理,应用就会卡顿。
Dart的解决方案是 Isolate。
- 不共享内存 :每个Isolate都有自己的独立内存堆(Heap)和事件循环 。Isolate之间不共享任何状态,通信的唯一方式是通过消息传递(Passing Messages) 。
- 真正的并行:由于现代设备是多核CPU,不同的Isolate可以被调度到不同的CPU核心上真正地并行运行。
- 通信成本 :因为不共享内存,Isolate之间传递消息时,数据会发生拷贝 。虽然对于简单数据很快,但对于大型数据(如图片、大列表),这个拷贝成本会很高。通常使用
SendPort
和ReceivePort
进行通信。
在Flutter中,你可以使用Isolate.spawn()
或更高级的compute()
函数来将繁重任务抛到新的Isolate中执行。
代码演示:
dart
import 'package:flutter/foundation.dart';
// 使用 compute (Flutter提供的简便API)
void main() async {
var result = await compute(heavyTask, 1000000000);
print(result);
}
// 这个函数会在新的Isolate中执行
int heavyTask(int count) {
int sum = 0;
for (int i = 0; i < count; i++) {
sum += i;
}
return sum;
}