Flutter - Dart事件循环机制与异步
# 一、Dart 异步
与 JavaScript 一样, Dart 是基于 事件循环机制
的 单线程模型
, 所以 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<T>
的函数:
factory Future(FutureOr<T> computation()) {
...
}
这个 FutureOr<T>
是一个联合类型, 最终类型可能是 Future 或是泛型 T 的具体类型. 当不指定泛型 T 时, 实例类型为 Future<dynamic>
. 下面是一个模拟网络耗时请求的例子:
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<T>
的函数, 但因为FutureOr<T>
是联合类型, 所以, 这里可以返回另一个 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,
("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 项目中才能运行.
# 参考资料
- 01
- Flutter - 子部件任意位置观察滚动数据11-24
- 02
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 03
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21