Flutter 异步机制:微任务队列 vs 事件队列
在 Dart/Flutter 的事件循环(Event Loop)中,任务队列分为两个优先级:微任务队列(Microtask Queue) 和 事件队列(Event Queue)。
核心原则是:微任务队列优先级 > 事件队列 。 每次执行完一段同步代码后,Dart 会先清空微任务队列中的所有 任务,然后再去事件队列取一个任务执行。
以下是具体的分类:
1. 微任务队列 (Microtask Queue)
这里面的任务非常"急",通常用于内部状态的更新,或者希望在下一次 UI 渲染或 IO 操作前立即执行的代码。
scheduleMicrotask():dart:async库中直接调度微任务的方法。Future.microtask(): 创建一个旨在微任务中运行的 Future。Future.then()/.catchError()/.whenComplete()的回调:- 当一个 Future 完成(complete)时,它的后续回调方法会被加入微任务队列。
- 注意 :如果是
Future.value()这种立即完成的 Future,它的.then也会立刻进入微任务队列。
async/await的恢复执行:- 当
await等待的 Future 完成后,async函数体内的后续代码(恢复执行部分)实际上被视为微任务调度。
- 当
2. 事件队列 (Event Queue)
这里主要包含外部事件、I/O 和定时器,是大部分异步任务发生的地方。
- I/O 事件: 文件读写完成、网络请求返回数据等。
- 定时器 :
Future.delayed()、Timer.run()、Timer.periodic()。- 注意 :
Future.delayed即使延时为 0,也是进入事件队列,因此它肯定比微任务晚执行。
- 注意 :
Future()构造函数 :- 例如
Future(() { ... }),这种普通的构造函数会将任务放入事件队列。
- 例如
- UI / 手势事件: 点击、滑动、键盘输入等(由 Engine 层传递给 Framework)。
- 绘制指令: VSync 信号到来时触发的绘制任务。
- Isolate 消息 : 其他 Isolate 发送到
ReceivePort的消息。
简单对比总结
| 任务来源 | 队列归属 | 优先级 |
|---|---|---|
scheduleMicrotask(...) |
Microtask | 高 (插队执行) |
Future.microtask(...) |
Microtask | 高 |
Future.then(...) |
Microtask | 高 |
Future.value(...).then |
Microtask | 高 |
Timer.run(...) |
Event | 低 (排队执行) |
Future.delayed(...) |
Event | 低 |
Future(() => ...) |
Event | 低 |
| 点击、IO、网络 | Event | 低 |
避坑指南
⚠️ 风险提示 :如果在微任务队列中无限循环添加微任务(例如递归调用 scheduleMicrotask),那么事件队列永远没有机会执行。 会导致:
- App 界面卡死(因为 UI 绘制事件在事件队列)。
- 点击无反应(手势事件在事件队列)。
- 网络请求不回调(IO 事件在事件队列)。
代码示例
dart
import 'dart:async'; // 必须导入这个库
void main() {
print('1. 同步代码开始');
// 事件队列
Future(() => print('6. Future() 去事件队列'));
Future.delayed(Duration.zero, () => print('7. Future.delayed 去事件队列'));
// 微任务队列
scheduleMicrotask(() => print('3. scheduleMicrotask 去微任务队列'));
Future.microtask(() => print('4. Future.microtask 去微任务队列'));
// Future.value 是立即完成,.then 进入微任务
Future.value().then((_) => print('5. Future.value().then 去微任务队列'));
print('2. 同步代码结束');
}
打印结果:
