Skip to content

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 / isolatedart-event-loop-isolateevent_loop_isolate_demo.dart

学习顺序 ​

建议按下面顺序复习:

  1. 先读本页:建立 Dart 并发整体心智模型,并先完成工具选型
  2. 再看专题页:按“事件循环 → Future / await → isolate / Flutter 落位”拆开理解
  3. 再跑 event loop / isolate lab:看同步、microtask、event queue、await 恢复点、Isolate.run 的实际顺序
  4. 最后对照 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 专题导航 ​

现在先做最小拆分:总览页保留概念地图、场景和对照表;专题页先提供学习入口、关键问题和延伸方向,后续再逐步补细节。

为什么需要 ​

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。

如果只会“Future 就是异步”,遇到这些问题就很难说清:

  • 明明用了 Future,为什么 UI 还是卡?
    • 一句话答:Future 只改变调度时机、不搬走 CPU 重活,主 isolate 仍被占住,所以照卡。
  • 为什么某个回调顺序和自己想的不一样?
    • 一句话答:恢复顺序由事件循环队列(microtask 优先于 event queue)决定,不是代码书写顺序。
  • 为什么大 JSON 解析只包一层 Future 还是不够?
    • 一句话答:解析仍发生在主 isolate,包 Future 只是延后执行;要把重活真正搬走得上 isolate / compute。

底层机制总览 ​

这一节现在只保留总览地图;具体执行顺序、输出说明和 Flutter 落位已拆到专题页,避免单页越写越重。

1. 默认执行模型:单线程事件循环 ​

Dart 默认在一个 isolate 内运行,核心调度模型是:

  1. 同步代码先跑完
  2. 再清空 microtask queue
  3. 再从 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 对照 ​

维度DartFlutterJS / WebJava / Kotlin 后台
默认异步模型单 isolate 事件循环UI isolate 事件循环event loop线程池 / 协程 / 调度器
更高优先级队列microtaskmicrotaskmicrotask / promise job无统一等价队列
普通异步队列event queueevent queuemacrotaskexecutor / dispatcher
真正并发隔离单元isolateisolate / computeWeb WorkerThread / process
最大误区把 Future 当线程以为 Future 就不会卡 UI把 Promise 当线程切换把共享内存思维直接套进 isolate 模型

常见场景 ​

1. 页面启动时多个 Future 排序不符合预期 ​

如果你同时用了:

  • scheduleMicrotask
  • Future.microtask
  • Future(() {})

执行顺序常常和直觉不一样。

建议先看:Dart EventLoop、Microtask Queue 与 Event Queue 机制

2. Flutter UI 卡顿 ​

如果 CPU 密集计算放在主 isolate:

  • Future 语法看起来是异步
  • 但本质仍可能阻塞当前 isolate 的执行

这时要考虑:

  • Isolate.run
  • compute

建议先看:Dart Isolate、compute() 与无共享多核并发

3. 大量 JSON / 加解密 / 数据清洗 ​

这类任务更适合 isolate,而不是只靠普通 Future 包一层。

同时要结合 Flutter 场景理解:

  • 是不是已经影响首帧或滚动流畅度
  • 任务结果是否需要频繁与 UI 往返
  • compute 能否满足当前宿主边界

常见坑 ​

坑现象修法
把 Future 当线程以为不会卡 UI先分清是否仍在当前 isolate
滥用 microtask某些回调顺序异常、可读性差只在必须抢优先级时使用
所有异步任务都上 isolate复杂度高、消息传递成本增加只给 CPU 密集任务上 isolate
认为 await 会立即切线程回来顺序理解错误理解它只是等待 future 完成后恢复 continuation
只因为“是异步”就把重计算留在主 isolateUI 依旧掉帧把 CPU 密集任务迁移到 isolate / compute

与相近概念对比 ​

概念本质适合场景
Future(() {})event queue 任务普通异步调度
Future.microtaskmicrotask queue 任务需要更高优先级的短逻辑
awaitcontinuation 恢复点顺序化异步代码
Isolate.run独立 isolateCPU 密集任务
Kotlin suspend挂起恢复 + 调度器多线程协程模型
Java Future结果句柄线程池任务结果回收

对应实验 ​

Lab说明状态
dart-event-loop-isolateevent loop / microtask / event queue / await / isolate已有

复习检查题 ​

  1. 为什么说 Future 不是线程?

    答:因为普通 Future 默认只是把任务放进当前 isolate 的调度队列,不会自动创建独立执行单元;如果任务本身是 CPU 重活,仍会占住当前 isolate。

  2. Future.microtask 为什么通常先于 Future(() {}) 执行?

    答:因为前者进入 microtask queue,后者进入 event queue;当前同步段结束后,Dart 会先清空 microtask queue,再处理 event queue。

  3. await 的本质是什么?

    答:是 async 函数在某个 Future 上挂起,并在该 Future 完成后把 continuation 重新排队恢复执行,而不是线程切换语义。

  4. 什么场景下必须优先考虑 isolate?

    答:CPU 密集型任务已经明显影响主 isolate 响应,例如大 JSON 解析、图片处理、加解密、复杂数据清洗等,就应优先考虑 isolate。

  5. Flutter 页面卡顿时,什么时候该怀疑主 isolate 被 CPU 任务占住?

    答:当网络请求本身已经异步化,但首帧、动画、滚动或页面交互仍持续掉帧,同时主线程上存在解析、计算、转换等重活时,就要重点怀疑主 isolate 被 CPU 任务占住。

速记 ​

  • Future 是调度,不是线程
  • await 是挂起恢复,不是线程切换
  • microtask 优先于 event queue
  • isolate 才是 Dart 真正的并发隔离单元
  • Flutter 卡不卡,关键看重活是不是还留在主 isolate

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