Appearance
Dart 并发总览:Future / event loop / isolate
在模块中的位置:本页作为 Dart 并发总览,负责先建立“事件循环 + 队列 + isolate”的概念地图,再进入专题页细看。
相关主题:Kotlin 协程主文档 · Java Thread / Future · Java 线程池
一句话定位
Dart 的并发核心不是“线程池 + 回调地狱”,而是 单线程事件循环 + microtask queue + event queue + isolate 消息隔离:普通 Future 默认仍在当前 isolate 的事件循环中调度;真正独立的并发隔离单元是 isolate,而不是 Future 本身。
代码索引
| 主题 | Lab 说明 | 源码 |
|---|---|---|
| event loop / microtask / event queue / Future / isolate | dart-event-loop-isolate | event_loop_isolate_demo.dart |
学习顺序
建议按下面顺序复习:
- 先读本页:建立 Dart 并发整体心智模型,并先完成工具选型
- 再看专题页:按“事件循环 → Future / await → isolate / Flutter 落位”拆开理解
- 再跑 event loop / isolate lab:看同步、microtask、event queue、
await恢复点、Isolate.run的实际顺序 - 最后对照 Kotlin / Java:明确 Dart isolate 模型和 Java / Kotlin 共享内存模型的本质差异
先判断你要哪种工具
| 诉求 | 首选 | 为什么 |
|---|---|---|
| 普通异步等待,不做重 CPU 计算 | Future / await | 只是等结果,不需要额外 isolate |
| 单次 CPU 重活,Flutter UI 场景 | compute | 快速把会卡 UI 的纯计算搬到后台 isolate |
| 单次 CPU 重活,纯 Dart 通用 | Isolate.run | 不依赖 Flutter,语义直接,适合一次性后台计算 |
| 长生命周期后台 worker / 多轮消息通信 | Isolate.spawn | 需要自己管理端口和消息协议,适合长期协作 |
这张表先解决一个最常见误区:
Future解决的是异步等待与恢复顺序,不是 CPU 并发隔离。compute/Isolate.run解决的是单次重计算不要堵主 isolate。Isolate.spawn解决的是长期后台 worker 与双向通信。
选型总览图
图上可以先这样读:
- 只是等网络、文件、Timer,不做重计算 →
Future/await。 - 单次 CPU 重活,怕卡 UI →
compute或Isolate.run。 - 长期后台 worker、多轮双向通信 →
Isolate.spawn。
Android 经验迁移:可以先这样类比
如果你是 Android 背景,可以先用下面这组近似映射建立直觉:
- Handler / 主线程异步回调 / Retrofit 异步等待 → 对应 Dart 的
Future/await - 后台线程里做一次重活,再把结果切回主线程 → 对应 Dart 的
compute/Isolate.run - 常驻 HandlerThread / 长期后台服务 / 持续消费队列 → 对应 Dart 的
Isolate.spawn
但要注意这只是帮助理解的近似映射,不是完全等价:
- Android 更常见的是共享内存 + 线程切换。
- Dart isolate 更强调内存隔离 + 消息传递。
所以类比能帮助你快速入门,但不要把 JVM 线程池 / Handler 的共享内存思维直接套到 isolate 上。
Dart 专题导航
- Dart 事件循环与队列:event loop / microtask / event queue
- Dart Future / await:调度、恢复与顺序错觉
- Dart isolate / Flutter compute:真正的并发隔离
- Dart Stream:异步数据序列 / 生成器 / 背压
- Dart Isolate 消息传递:SendPort / ReceivePort
- Flutter 引擎线程模型:UI isolate / raster / platform / IO
现在先做最小拆分:总览页保留概念地图、场景和对照表;专题页先提供学习入口、关键问题和延伸方向,后续再逐步补细节。
为什么需要
Flutter / Dart 面试和实际业务里,这些问题几乎必问:
- 为什么
Future不是线程?- 一句话答:
Future只是把任务排进当前 isolate 的调度队列,不创建独立执行单元;任务本身仍跑在当前 isolate / 线程上。
- 一句话答:
Future.microtask和Future(() {})到底差在哪?- 一句话答:前者进 microtask queue(当前同步段结束后先清空),后者进 event queue(下一轮事件循环处理),所以 microtask 更早执行。
await后面的代码什么时候恢复?- 一句话答:
await挂起当前 async 函数,等Future完成、事件循环把 continuation 重新排队后恢复(仍在同一 isolate)。
- 一句话答:
- CPU 密集任务为什么会卡 UI?
- 一句话答:因为
Future默认不换执行单元,CPU 重活仍占住主 isolate,事件循环被堵,UI 渲染得不到执行。
- 一句话答:因为
- 什么时候必须上 isolate?
- 一句话答:CPU 重活(大 JSON 解析、图片处理、加解密等)已明显影响主 isolate 响应时,必须搬到
Isolate.run/compute。
- 一句话答:CPU 重活(大 JSON 解析、图片处理、加解密等)已明显影响主 isolate 响应时,必须搬到
如果只会“Future 就是异步”,遇到这些问题就很难说清:
- 明明用了
Future,为什么 UI 还是卡?- 一句话答:
Future只改变调度时机、不搬走 CPU 重活,主 isolate 仍被占住,所以照卡。
- 一句话答:
- 为什么某个回调顺序和自己想的不一样?
- 一句话答:恢复顺序由事件循环队列(microtask 优先于 event queue)决定,不是代码书写顺序。
- 为什么大 JSON 解析只包一层
Future还是不够?- 一句话答:解析仍发生在主 isolate,包
Future只是延后执行;要把重活真正搬走得上 isolate /compute。
- 一句话答:解析仍发生在主 isolate,包
底层机制总览
这一节现在只保留总览地图;具体执行顺序、输出说明和 Flutter 落位已拆到专题页,避免单页越写越重。
1. 默认执行模型:单线程事件循环
Dart 默认在一个 isolate 内运行,核心调度模型是:
- 同步代码先跑完
- 再清空 microtask queue
- 再从 event queue 取任务执行
所以常见优先级大致是:
text
sync code
> microtask
> event queue future / timer / IO completion关键结论:
- 同一 isolate 内不存在多段 Dart 代码真正并行执行
- 不是“谁先写谁先执行”,而是先按队列层级,再按各自入队顺序
- microtask 会优先于普通 event 被清空
详见:Dart EventLoop、Microtask Queue 与 Event Queue 机制
2. Future 解决的是异步调度,不是新线程
像下面这种:
dart
Future(() {
// ...
});默认只是把任务丢进当前 isolate 的 event queue,不是新线程。
也就是说:
- 它会延后执行
- 但仍然在当前 isolate 的调度模型里跑
- 如果任务本身是 CPU 重活,照样可能卡住这个 isolate
await 的本质也不是“切线程回来”,而是:
- 当前 async 函数在这里挂起
- 等对应 Future 完成
- 然后把 continuation 重新排队并恢复后半段执行
详见:Dart Future、async/await 拆解与单线程并发
3. isolate 才是真正的并发隔离单元
isolate 特点:
- 独立内存
- 不共享对象
- 靠消息传递
- 更适合 CPU 密集任务
这也是 Flutter 中为什么:
- 网络请求异步通常不需要 isolate
- 但大 JSON 解析、图片处理、复杂计算有时必须 isolate
继续细分时,可以先这样理解:
compute:Flutter 提供的高层便捷入口,适合单次 CPU 重活。Isolate.run:Dart 原生的一次性后台执行入口。Isolate.spawn:长期 isolate + 端口通信,适合常驻 worker。
详见:Dart Isolate、compute() 与无共享多核并发 · Dart Isolate 消息传递:SendPort / ReceivePort / 可发送对象
Android / Flutter / Web / Backend 对照
| 维度 | Dart | Flutter | JS / Web | Java / Kotlin 后台 |
|---|---|---|---|---|
| 默认异步模型 | 单 isolate 事件循环 | UI isolate 事件循环 | event loop | 线程池 / 协程 / 调度器 |
| 更高优先级队列 | microtask | microtask | microtask / promise job | 无统一等价队列 |
| 普通异步队列 | event queue | event queue | macrotask | executor / dispatcher |
| 真正并发隔离单元 | isolate | isolate / compute | Web Worker | Thread / process |
| 最大误区 | 把 Future 当线程 | 以为 Future 就不会卡 UI | 把 Promise 当线程切换 | 把共享内存思维直接套进 isolate 模型 |
常见场景
1. 页面启动时多个 Future 排序不符合预期
如果你同时用了:
scheduleMicrotaskFuture.microtaskFuture(() {})
执行顺序常常和直觉不一样。
建议先看:Dart EventLoop、Microtask Queue 与 Event Queue 机制
2. Flutter UI 卡顿
如果 CPU 密集计算放在主 isolate:
Future语法看起来是异步- 但本质仍可能阻塞当前 isolate 的执行
这时要考虑:
Isolate.runcompute
建议先看:Dart Isolate、compute() 与无共享多核并发
3. 大量 JSON / 加解密 / 数据清洗
这类任务更适合 isolate,而不是只靠普通 Future 包一层。
同时要结合 Flutter 场景理解:
- 是不是已经影响首帧或滚动流畅度
- 任务结果是否需要频繁与 UI 往返
compute能否满足当前宿主边界
常见坑
| 坑 | 现象 | 修法 |
|---|---|---|
把 Future 当线程 | 以为不会卡 UI | 先分清是否仍在当前 isolate |
| 滥用 microtask | 某些回调顺序异常、可读性差 | 只在必须抢优先级时使用 |
| 所有异步任务都上 isolate | 复杂度高、消息传递成本增加 | 只给 CPU 密集任务上 isolate |
认为 await 会立即切线程回来 | 顺序理解错误 | 理解它只是等待 future 完成后恢复 continuation |
| 只因为“是异步”就把重计算留在主 isolate | UI 依旧掉帧 | 把 CPU 密集任务迁移到 isolate / compute |
与相近概念对比
| 概念 | 本质 | 适合场景 |
|---|---|---|
Future(() {}) | event queue 任务 | 普通异步调度 |
Future.microtask | microtask queue 任务 | 需要更高优先级的短逻辑 |
await | continuation 恢复点 | 顺序化异步代码 |
Isolate.run | 独立 isolate | CPU 密集任务 |
Kotlin suspend | 挂起恢复 + 调度器 | 多线程协程模型 |
Java Future | 结果句柄 | 线程池任务结果回收 |
对应实验
| Lab | 说明 | 状态 |
|---|---|---|
| dart-event-loop-isolate | event loop / microtask / event queue / await / isolate | 已有 |
复习检查题
为什么说
Future不是线程?答:因为普通
Future默认只是把任务放进当前 isolate 的调度队列,不会自动创建独立执行单元;如果任务本身是 CPU 重活,仍会占住当前 isolate。Future.microtask为什么通常先于Future(() {})执行?答:因为前者进入 microtask queue,后者进入 event queue;当前同步段结束后,Dart 会先清空 microtask queue,再处理 event queue。
await的本质是什么?答:是 async 函数在某个
Future上挂起,并在该Future完成后把 continuation 重新排队恢复执行,而不是线程切换语义。什么场景下必须优先考虑 isolate?
答:CPU 密集型任务已经明显影响主 isolate 响应,例如大 JSON 解析、图片处理、加解密、复杂数据清洗等,就应优先考虑 isolate。
Flutter 页面卡顿时,什么时候该怀疑主 isolate 被 CPU 任务占住?
答:当网络请求本身已经异步化,但首帧、动画、滚动或页面交互仍持续掉帧,同时主线程上存在解析、计算、转换等重活时,就要重点怀疑主 isolate 被 CPU 任务占住。
速记
Future是调度,不是线程await是挂起恢复,不是线程切换- microtask 优先于 event queue
- isolate 才是 Dart 真正的并发隔离单元
- Flutter 卡不卡,关键看重活是不是还留在主 isolate