字节笔记本
2026年2月22日
Flutter - Dart事件循环机制与异步
与 JavaScript 一样,Dart 是基于事件循环机制的单线程模型,所以 Dart 中没有多线程,也就没有主线程与子线程之分。本文将深入介绍 Dart 的异步编程机制,包括事件循环、Future、async/await 以及 isolate 等核心概念。
一、Dart 异步
1、同步与异步
- 同步:同一线程中,按照代码的编写顺序,自上而下依次执行(直观感受:需要等待)
- 异步:代码执行中,某段代码的执行不会影响后面代码的执行(直观感受:无需等待)
2、单线程模型
单线程模型
- 一条执行线上,同时且只能执行一个任务(事件),其他任务都必须在后面排队等待被执行
- 为了不阻碍代码的执行,每遇到的耗时任务都会被挂起放入任务队列,待执行结束后再按放入顺序依次执行队列上的任务,从而达到异步效果
单线程模型与多线程各自的优势
- 单线程模型的优势:避免了多线程的缺点,比较适合于需要等待对方传送数据或返回结果的耗时操作,如网络请求、IO 操作等
- 多线程的优势:尽可能利用处理器的多核实现并行计算的计算密集型操作
多线程的缺点
- 会带来额外的资源和性能消耗
- 多个线程操作共享内存时需要加锁控制,锁竞争会降低性能和效率,复杂情况下还容易造成死锁
3、事件循环机制
对于用户点击、滑动、硬盘 IO 访问等事件,你不知道何时发生或以什么顺序发生,所以得有一个永不停歇且不能阻塞的循环来等待处理这些"突发"事件。于是,基于事件循环机制的单线程模型就出现了。
Dart 事件循环机制由一个**消息循环(Event Looper)和两个消息队列(Event Queue)**构成,这两个消息队列分别是:事件队列(Event Queue)和微任务队列(MicroTask Queue)。
Event Looper
Dart 在执行完 main 函数后,Event Looper 就开始工作,Event Looper 优先全部执行完 Microtask Queue 中的 event,直到 Microtask Queue 为空时,才会执行 Event Looper 中的 event,Event Looper 为空时才可以退出循环。
注意:Event Looper 为空时,是可以而不是一定要退出,视场景而定。
Event Queue
Event Queue 的 event 来源于外部事件和 Future:
- 外部事件:例如输入/输出、手势、绘制、计时器、Stream 等
- Future:用于自定义 Event Queue 事件
对于外部事件,一旦没有任何 microtask 要执行,Event loop 才会考虑 event queue 中的第一项,并且将会执行它。
通过 Future 实例向 Event Queue 添加事件:
Future(() {
// 事件任务
});Microtask Queue
- Microtask Queue 的优先级高于 Event Queue
- 使用场景:想要在稍后完成一些任务(microtask)但又希望在执行下一个事件(event)之前执行
Microtask 一般用于非常短的内部异步动作,并且任务量非常少。如果微任务非常多,就会造成 Event Queue 排不上队,会阻塞 Event Queue 的执行(如:用户点击没有反应)。所以,大多数情况下优先考虑使用 Event Queue,整个 Flutter 源代码仅引用 scheduleMicroTask() 方法 7 次。
通过 scheduleMicrotask() 函数向 Microtask Queue 添加任务:
scheduleMicrotask(() {
// 微任务
});二、Future
Dart 中的异步操作主要使用 Future 与 async/await,整体与前端 ES6 中的 Promise、async/await 的使用差不多,可以把 Future 理解为是一个自带 callback 效果的类。
1、基本使用
通过查看 Future 的构造函数知道,创建时需要传入一个返回值类型是 FutureOr 的函数:
factory Future(FutureOr<T> computation()) {
...
}这个 FutureOr 是一个联合类型,最终类型可能是 Future 或是泛型 T 的具体类型。当不指定泛型 T 时,实例类型为 Future。下面是一个模拟网络耗时请求的例子:
Future<String> getNetworkData() {
// 1. 将耗时操作包裹到Future的回调函数中
return Future<String>(() {
sleep(Duration(seconds: 2));
return "Hello lqr"; // 只要有返回结果,那么就执行Future对应的then的回调(相当于Promise-resolve)
// throw Exception("error"); // 如果没有结果返回(有错误信息),需要在Future回调中抛出一个异常(相当于Promise-reject)
});
}Future 实例有 3 个常用方法:
- then((value){...}):正常运行时执行
- catchError((err){...}):出现错误时执行
- whenComplete((){...}):不管成功与否都会执行
通过以上 3 个方法,即可获得 Future 实例的执行状况与结果:
main(List<String> args) {
print("main start");
// 2. 拿到结果
var future = getNetworkData();
future
.then((value) => print(value)) // Hello lqr
.catchError((err) => print(err))
.whenComplete(() => print("执行完成")); // 不管成功与否都会执行
print("main end");
}日志输出如下:
main start
main end
// 2秒后输出:
Hello lqr
执行完成注意,以上 3 个方法是可以分开写的,但每次执行完一个方法时需要对 future 实例重新赋值(相当于包了一层),否则后续方法无效:
var future = getNetworkData();
// 错误写法:
future.then((value) => print(value));
future.catchError((error) => print(error)); // 无效
// 正确写法:
future = future.then((value) {
print(value);
return value;
});
future.catchError((error) => print(error)); // 有效2、链式调用
Future 可以在 then() 方法中返回另一个 Future 实例,从而达到链式调用的效果,这对那些有数据关联的网络请求很有用:
main(List<String> args) {
print("main start");
// 链式调用,执行多个数据处理
Future(() {
return "第一次结果";
}).then((value) {
print(value);
return "第二次结果";
}).then((value) {
print(value);
return "第三次结果";
}).then((value) {
print(value);
}).catchError((error) {
print(error);
});
print("main end");
}强调:Future 构造函数要求传入一个返回值类型是 FutureOr 的函数,但因为 FutureOr 是联合类型,所以,这里可以返回另一个 Future 实例,或者是一个具体类型数据,比如字符串。
3、其它 API
Future 除了默认构造器外,还提供了几个常用的命名构造器:
- Future.value():创建一个返回具体数据的 Future 实例
- Future.error():创建一个返回错误的 Future 实例
- Future.delayed():创建一个延时执行的 Future 实例
main(List<String> args) {
print("main start");
Future.value("Hello lqr").then((value) => print(value));
Future.error("出错了").catchError((error) => print(error));
Future.delayed(Duration(seconds: 3))
.then((value) => "Hello lqr")
.then((value) => print(value));
Future.delayed(Duration(seconds: 2), () => "Hello lqr")
.then((value) => print("welcome"))
.then((value) => throw Exception("出错了"))
.catchError((error) => print(error))
.whenComplete(() => print("执行完成")); // 不管成功与否都会执行
print("main end");
}三、async/await
async/await 是 Dart 提供的可以用同步的代码格式实现异步的调用过程的语法糖。
1、基本使用
- await 必须在 async 函数中使用
- async 函数返回的结果必须是一个 Future
Future getNetworkData() async {
var userId = await getUserId();
var userInfo = await getUserInfo(userId);
return userInfo.username // 会自动包裹成Future
}如果不使用 async/await,那么上面的代码则需要这么写:
Future getNetworkData() {
return getUserId().then((userId) {
return getUserInfo(userId);
}).then((userInfo) {
return userInfo.username;
});
}相比之下,使用 async/await 写出来的代码在理解上会更加清晰。
四、isolate
所有的 Dart 代码都是在 isolate 中运行的,它就是机器上的一个小空间,具有自己的私有内存块和一个运行着 Event Looper 的单个线程。每个 isolate 都是相互隔离的,并不像线程那样可以共享内存。一般情况下,一个 Dart 应用只会在一个 isolate 中运行所有代码,但如果有特殊需要,可以开启多个。
注意:Dart 中没有线程的概念,只有 isolate。
1、创建 isolate(Dart API)
Dart 默认提供了 Isolate.spawn(entryPoint, message) 用于开启 isolate,通过源码可以知道形参 message 其实是形参 entryPoint 对应的函数执行时需要的参数:
external static Future<Isolate> spawn<T>(
void entryPoint(T message),
T message, {
bool paused = false,
bool errorsAreFatal = true,
SendPort? onExit,
SendPort? onError,
@Since("2.3") String? debugName
});使用 Isolate.spawn(entryPoint, message) 开启 isolate,并指定要执行的任务:
import 'dart:isolate';
main(List<String> args) {
print("main start");
Isolate.spawn(calc, 100);
print("main end");
}
void calc(int count) {
var total = 0;
for (var i = 0; i < count; i++) {
total += i;
}
print(total);
}2、isolate 通信(单向)
isolate 间可以一起工作的唯一方法是通过来回传递消息。一般情况下,子 isolate 会将运行结果通过管道以消息的形式发送到 主 isolate,并在 主 isolate 的 Event Looper 中处理该消息,这时就需要借助 ReceivePort 来处理消息的传递了:
- 在启动 子 isolate 时,将 主 isolate 的发送管道(SendPort)作为参数传递给 子 isolate
- 子 isolate 在执行完毕时,可以利用管道(SendPort)给 主 isolate 发送信息
import 'dart:isolate';
main(List<String> args) async {
print("main start");
// 1. 创建管道
var receivePort = ReceivePort();
// 2. 创建isolate
Isolate isolate = await Isolate.spawn(foo, receivePort.sendPort);
// 3. 监听管道
receivePort.listen((message) {
print(message);
// 不再使用时,关闭管道
receivePort.close();
// 不再使用时,将 isolate 杀死
isolate.kill();
});
print("main end");
}
void foo(SendPort sendPort) {
sendPort.send("Hello lqr");
}以上只实现了 isolate 的单向通信,双向通信比较麻烦,有兴趣可以再查看一些其他资料。
3、创建 isolate(Flutter API)
Flutter 提供了更为方便的开启 isolate 的 API:compute() 函数。以下是示例代码:
main(List<String> args) async {
int result = await compute(powerNum, 5);
print(result);
}
int powerNum(int num) {
return num * num;
}compute() 是 Flutter 的 API,不是 Dart 的 API,所以,上面的代码只能在 Flutter 项目中才能运行。
参考资料
- Flutter 开发之 Dart 线程与异步
- Dart asynchronous programming: Isolates and event loops
- Futures - Isolates - Event Loop
原文作者:GitLqr 原文链接:https://fullstackaction.com/pages/0fcdc8/ 来源:FSA全栈行动