Dart async/await 实现机制深度解析:从状态机到事件循环,拆开给你看
async/await 本质是语法糖:拆开状态机与事件循环,讲清 await 如何挂起恢复、与 Future 的关系。 掌握后能说清「await 背后发生了什么」以及和微任务的执行顺序。
一句话概括
async/await 不是魔法——编译器把每个 async 函数编译成一个状态机,每个 await 是切分点,事件循环按”微任务先于事件队列”的规则调度状态机的执行,这就是 Dart 单线程不卡顿的底层原因。
核心知识点
1. 事件循环的执行顺序:微任务 → 事件队列
1
2
3
4
5
6
7
8
void main() {
print('1');
Future(() => print('4')); // 事件队列
Future.microtask(() => print('2')); // 微任务
Future.value('3').then(print); // 微任务
print('5');
}
// 输出:1 → 5 → 2 → 3 → 4
规则:每轮事件循环先清空所有微任务,再从事件队列取一个。Future.value().then() 回调在微任务,Future() 构造函数体在事件队列。这是面试必考题。
2. async 函数编译为状态机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 你写的
Future<int> compute(int a, int b) async {
final x = await fetchA(); // 挂起点1
final y = await fetchB(x); // 挂起点2
return y + b;
}
// 编译后等价于(简化)
Future<int> compute(int a, int b) {
var state = 0; dynamic result;
final c = Completer<int>();
void step() {
switch (state) {
case 0:
state = 1;
fetchA().then((v) { result = v; step(); });
break;
case 1:
state = 2;
fetchB(result).then((v) { result = v; step(); });
break;
case 2:
c.complete(result + b);
}
}
step();
return c.future;
}
每个 await = 一个 case,then 回调推进状态机。这就是为什么 await 不占线程——它只是注册回调然后让出控制权。
3. mount / dispose 与 async 的致命组合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ❌ 常见错误:Widget 销毁后还调用 setState
@override
void initState() {
super.initState();
fetchUser().then((user) {
// Widget 可能已经 dispose 了!
setState(() => this.user = user); // 运行时错误!
});
}
// ✅ 正确做法:
@override
void initState() {
super.initState();
_userFuture = fetchUser();
}
// 在 build 中用 FutureBuilder 或检查 mounted
核心问题:async 回调可能在 Widget 销毁后才执行。Flutter 中用 mounted 检查或 FutureBuilder 来保证安全。
4. await + try/catch = 同步式错误处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Future<void> loadData() async {
try {
final user = await api.fetchUser();
final posts = await api.fetchPosts(user.id);
print('加载完成');
} on TimeoutException {
print('请求超时,使用缓存数据');
} catch (e, st) {
print('未知错误: $e');
debugPrintStack(stackTrace: st);
} finally {
hideLoading(); // 无论成败都关 loading
}
}
async 函数内 try/catch 能捕获 await 抛出的异常,写起来像同步代码。比 then 链的 catchError 更直观。
5. Future 的调度时机总结
1
2
3
4
5
// 四种调度方式,优先级从高到低:
// 1. 同步代码(main, sync*)
// 2. 微任务(scheduleMicrotask, Future.value().then, Future.microtask())
// 3. 事件队列(Future(), Future.delayed(), IO 回调)
// 4. Timer.run, Future.delayed(Duration(...))
一条黄金法则:Dart 永远不会中断正在执行的同步代码去跑微任务或事件队列。微任务只在当前调用栈清空后才执行。
其实你每天都在用
setState()不能放在 async 回调里:Flutter 报错 “setState called after dispose” 就是因为 async 回调执行时 Widget 已销毁await Future.delayed(Duration(seconds: 1)):这不是 sleep,不会卡 UIFutureBuilder:Flutter 内置的对 Future 状态的响应式封装,自动处理 loading/data/errorinitState中的WidgetsBinding.instance.addPostFrameCallback:在帧渲染完成后执行,比微任务更晚- 日志框架中
Future.microtask延迟错误上报:让当前帧正常渲染完再处理错误
常见误解(FAQ)
❌ 误区:「await 后面的代码一定在微任务里执行」
不一定。如果 await 的 Future 已经在事件队列中,那 await 后面的代码也在事件队列中执行。await Future(() => 1) 和 await Future.value(1) 的 then 回调在不同队列。
❌ 误区:「Dart 是单线程所以不能做真并发」
Dart 有 Isolate(隔离区),每个 Isolate 独立内存、独立事件循环。可以在后台 Isolate 做大量计算,UI Isolate 不受影响。compute() 函数就是 Isolate 的便捷封装。
❌ 误区:「for (var item in items) await process(item) 和 Future.forEach 一模一样」
基本一样都是串行。并行用 Future.wait(items.map(process))。
一句话总结
async/await = 编译器状态机 + 事件循环调度 = 单线程也能写出干净利落的异步代码。搞懂这个,80% 的 Flutter 异步 Bug 都能迎刃而解。