Skip to content

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 / awaitJavaScript ES6 Promise / awaitKotlin 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-isolateEventLoop / Microtask Queue / Event Queue / Isolate 调度顺序验证

复习检查题 ​

  1. 在 Dart 中,运行一个 async 函数时,await 关键字之前的代码是在哪个线程/队列中执行的?

    答:是在当前的同步调用栈(当前线程)中直接同步执行的。async 关键字不会创造子线程;只有当代码执行到第一个 await 表达式时,后半段代码才会被注册成类似 then() 的 continuation,并在对应 Future 完成后由当前 Isolate 的 EventLoop 恢复执行。

  2. await 和 then() 的本质关系是什么?

    答:await 可以理解为语法更直观的 then() continuation。二者都表示“前面的 Future 完成后继续”,都不代表创建新线程;差别主要在可读性和链式组织方式。

  3. 连续编写 await req1(); await req2(); 与使用 Future.wait([req1(), req2()]) 在性能上有何区别?

    答:连续使用 await 会导致两个请求串行执行(必须等 req1 返回后才开始发起 req2),总耗时为两者之和;而使用 Future.wait() 会让两个异步任务并发发起,总耗时仅取决于耗时较长的那一个请求,大幅提升界面数据加载速度。

速记 ​

  • await 前后界限:await 之前同步跑,遇到 await 脱糖为 .then() 闭包延迟跑。
  • 并发避坑:独立任务严禁连续 await 串行,统一使用 Future.wait 并发调度。
  • 错误捕获:使用 async/await 时,异步异常统一使用标准的 try-catch 捕获。

站点构建时间:2026/8/24 23:43:17