Appearance
Dart Future、async/await 拆解与单线程并发
回到总览:并发与运行时底座
相关模块:Dart EventLoop、Microtask Queue 与 Event Queue 机制 · Dart Isolate、compute() 与无共享多核并发
一句话定义
Future 是 Dart 中表示异步计算最终结果(成功值或 Error)的容器对象;而 async / await 是基于 Future 和状态机拆解的语法糖:在遇到 await 关键字之前代码同步执行,遇到 await 时编译器会把后半段代码注册成类似 then() 的 continuation;它不创造新线程,只是在当前 Isolate 的 EventLoop 中等待 Future 完成后再恢复执行。
代码索引
对应 Lab:dart-event-loop-isolate
为什么需要
- 为什么在
async函数内部,await之前的代码是同步执行的,只有await之后的代码才是异步执行的?- 一句话答:
async关键字本身并不改变当前调用栈的同步执行顺序;只有运行到await时,编译器才会把后半段代码注册成类似then()的 continuation,并把控制权交回当前 Isolate 的 EventLoop。
- 一句话答:
await和then()到底是什么关系,为什么它们看起来都像“稍后再继续”?- 一句话答:
await本质上就是语法更直观的then()continuation;两者都不是新线程,而是等前面的 Future 完成后,再把后续逻辑作为异步回调恢复执行。
- 一句话答:
底层机制
1. async / await 语法糖脱糖原理
原始代码:
dart
Future<void> loadData() async {
print('A');
await fetchNetwork();
print('B');
}可以近似理解成下面这段“continuation 风格”的代码:
dart
Future<void> loadData() {
print('A');
return fetchNetwork().then((_) {
print('B');
});
}这里最关键的不是“await 会不会开线程”,而是:
print('A')仍然在当前同步调用栈里立刻执行。- 遇到
await fetchNetwork()时,后半段print('B')会被注册成类似then()的 continuation。 - 当前函数先把控制权交还给 EventLoop;等
fetchNetwork()对应的 Future 完成后,再恢复执行后半段。 - 这个恢复动作发生在当前 Isolate 内,不会凭空切出新线程;至于恢复更贴近 Microtask 还是要先等某个 Event,到底取决于前面的 Future 是如何完成的。
then() 与 await 的统一理解
then():显式写出“Future 完成后继续执行这个回调”。await:用更直观的语法表达同一件事。- 二者都不是新的队列类型,也不是线程 API;它们只是 Future 完成后的 continuation 注册方式。
最短对比例子:
dart
Future<void> demoThen() {
return Future.value('ok').then((value) {
print('then: $value');
});
}
Future<void> demoAwait() async {
final value = await Future.value('ok');
print('await: $value');
}这两段在语义上都表达“先等 Future 完成,再继续后半段逻辑”,只是 await 更适合写顺序流程,then() 更适合链式拼装。
Dart Future、Java Future 与 Kotlin 协程里的对应物
三者都在表达“这个结果现在还没好,以后会好”,但默认绑定的运行时模型并不一样:
- Dart
Future:更偏“异步结果 + EventLoop continuation”,重点是 Future 完成后如何恢复后半段逻辑,而不是“已经开了一个线程”。 - Java
Future:更偏“线程池任务结果句柄”,常见来源是ExecutorService.submit(...);get()往往带阻塞语义。 - Kotlin 协程:更常用
suspend和Deferred;其中Deferred最像“协程版 Future”,但 Kotlin 日常更强调直接写suspend顺序代码,而不是显式持有 Future。
如果类比到 Android,可以先这样理解:
- Dart
Future更像suspend恢复点 / Promise 风格 continuation。 - Java
Future更像 线程池提交任务后拿到的结果提货单。 - Kotlin
Deferred更像 有结果的Job,而suspend则是现代 Android 更常见的主写法。
所以不要把 Dart Future 机械理解成“后台线程任务”;在 Dart 里,真正的 CPU 并发隔离单元是 Isolate,而 Future 更主要承担“异步结果何时恢复、如何续跑”的语义。
Android / Flutter / Web / Backend 对照
| 维度 | Dart Future / await | JavaScript ES6 Promise / await | Kotlin Coroutines |
|---|---|---|---|
| 底层实现 | 状态机 + Future continuation + EventLoop | 状态机 + Promise continuation / Microtask | 续体 Continuation + 状态机 |
| 错误捕获 | try-catch 或 .catchError() | try-catch 或 .catch() | try-catch 或 CoroutineExceptionHandler |
| 线程归属 | 默认在当前 Isolate 线程 | 默认在当前 V8 JS 线程 | 可由 Dispatchers 自由切线程 |
常见场景
1. 使用 Future.wait 实现多异步任务并行等待
dart
Future<void> loadPageData() async {
// 并发发起两个网络请求 (非串行 await!)
final results = await Future.wait([
fetchUserProfile(),
fetchUserBanners(),
]);
final profile = results[0] as UserProfile;
final banners = results[1] as List<Banner>;
renderUI(profile, banners);
}对应实验
各章节内已嵌入跳转链接;此处汇总全部 Lab:
| Lab | 说明 |
|---|---|
| dart-event-loop-isolate | EventLoop / Microtask Queue / Event Queue / Isolate 调度顺序验证 |
复习检查题
在 Dart 中,运行一个
async函数时,await关键字之前的代码是在哪个线程/队列中执行的?答:是在当前的同步调用栈(当前线程)中直接同步执行的。
async关键字不会创造子线程;只有当代码执行到第一个await表达式时,后半段代码才会被注册成类似then()的 continuation,并在对应 Future 完成后由当前 Isolate 的 EventLoop 恢复执行。await和then()的本质关系是什么?答:
await可以理解为语法更直观的then()continuation。二者都表示“前面的 Future 完成后继续”,都不代表创建新线程;差别主要在可读性和链式组织方式。连续编写
await req1(); await req2();与使用Future.wait([req1(), req2()])在性能上有何区别?答:连续使用
await会导致两个请求串行执行(必须等 req1 返回后才开始发起 req2),总耗时为两者之和;而使用Future.wait()会让两个异步任务并发发起,总耗时仅取决于耗时较长的那一个请求,大幅提升界面数据加载速度。
速记
- await 前后界限:await 之前同步跑,遇到 await 脱糖为
.then()闭包延迟跑。 - 并发避坑:独立任务严禁连续 await 串行,统一使用
Future.wait并发调度。 - 错误捕获:使用 async/await 时,异步异常统一使用标准的
try-catch捕获。