Dart异步
Dart异步
Dart 异步基础
Dart 单线程模型
Dart 是单线程的。
网络请求和 IO 读写不会阻塞单线程吗?
- 网络请求本身使用了 Socket 通信,而 Socket 本身提供了 select 模型,可以进行非阻塞方式的工作;
- 文件读写的 IO 操作,我们可以使用操作系统提供的基于事件的回调机制;
问题?
- 在多核 CPU 中,单线程是不是就没有充分利用 CPU 呢?
- 单线程是如何来处理网络通信、IO 操作它们返回的结果呢?答案就是事件循环(Event Loop)。
Dart 基于事件循环
什么是事件循环?
 Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是 “ 微任务队列 “ microtask queue,另一个叫做 “ 事件队列 “ event queue。从图中可以发现,微任务队列的执行优先级高于事件队列。 入口函数 main() 执行完后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执行微任务队列中的任务,事件任务执行完毕后程序便会退出,但是,在事件任务执行的过程中也可以插入新的微任务和事件任务,在这种情况下,整个线程的执行过程便是一直在循环,不会退出,而 Flutter 中,主线程的执行过程正是如此,永不终止。 在 Dart 中,所有的外部事件任务都在事件队列中,如 IO、计时器、点击、以及绘制事件等,而微任务通常来源于 Dart 内部,并且微任务非常少,之所以如此,是因为微任务队列优先级高,如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久,对于 GUI 应用来说最直观的表现就是比较卡,所以必须得保证微任务队列不会太长。值得注意的是,我们可以通过 Future.microtask(…) 方法向微任务队列插入一个任务。在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其他任务执行的。 |
Dart 异步
Dart 代码库中有大量返回 Future 或 Stream 对象的函数,这些函数都是 异步 的,它们会在耗时操作(比如 I/O)执行完毕前直接返回而不会等待耗时操作执行完毕。async
和 await
关键字用于实现异步编程,并且让你的代码看起来就像是同步的一样。
Future
Future API
.then
通过.then(成功回调函数) 的方式来监听 Future 内部执行完成时获取到的结果;.catchError
通过.catchError(失败或异常回调函数) 的方式来监听 Future 内部执行失败或者出现异常时的错误信息;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void main(List<String> args) {
var future = getNetworkData();
future
.then((value) {
print(value);
})
.then((value) => 1)
.then((value) => "10")
.then((value) => {print(value)})
.catchError((error) {
// 捕获出现异常时的情况
print(error);
});
}
Future<String> getNetworkData2() {
return Future<String>(() {
sleep(Duration(seconds: 3));
// 不再返回结果,而是出现异常
// return "network data";
throw Exception("network error.");
});
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
return "network data";
});
}
Future 的两种状态
- 未完成状态(uncompleted):执行 Future 内部的操作时(在上面的案例中就是具体的网络请求过程,我们使用了延迟来模拟),我们称这个过程为未完成状态
- 完成状态(completed):当 Future 内部的操作执行完成,通常会返回一个值,或者抛出一个异常,都是完成状态
其他 API:
Future.value(value)
直接获取一个完成的 Future,该 Future 会直接调用 then 的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
main(List<String> args) {
print("main function start");
Future.value("哈哈哈").then((value) {
print(value);
});
print("main function end");
}
// 输出
// main function start
// main function end
// 哈哈哈
哈哈哈是在最后打印的呢,因为 Future 中的 then 会作为新的任务会加入到事件队列中(Event Queue),加入之后你肯定需要排队执行了
Future.error(object)
直接获取一个完成的 Future,但是是一个发生异常的 Future,该 Future 会直接调用 catchError 的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
main(List<String> args) {
print("main function start");
Future.error(Exception("错误信息")).catchError((error) {
print(error);
});
print("main function end");
}
// main function start
// main function end
// Exception: 错误信息
Future.delayed(时间, 回调函数)
在延迟一定时间时执行回调函数,执行完回调函数后会执行 then 的回调
1
2
3
4
5
6
7
8
9
10
main(List<String> args) {
print("main function start");
Future.delayed(Duration(seconds: 3), () {
return "3秒后的信息";
}).then((value) {
print(value);
});
print("main function end");
}
async、await
async、await 介绍
- Future 可以做到不阻塞我们的线程,让线程继续执行,并且在完成某个操作时改变自己的状态,并且回调 then 或者 errorCatch 回调
- 通常一个 async 的函数会返回一个 Future
- await 必须要在 async 代码块中
案例
异步代码 Future 改造
1
2
3
4
5
6
7
8
9
Future<String> getNetworkData3() async {
// The await expression can only be used in an async function.
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "请求到的数据:" + result;
}
getNetworkData3().then((value) => print(value));
案例 2
1
2
3
4
5
6
7
8
9
10
11
12
13
static Future<List<Map<String, Object>>> list(int page, int size) async {
return List<Map<String, Object>>.generate(size, (index) {
return {
'title': '标题${index + (page - 1) * size + 1}:这是一个列表标题,最多两行,多处部分将会被截取',
'imageUrl':
'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3331308357,177638268&fm=26&gp=0.jpg',
'viewCount': 180,
};
});
}
// 调用
// _currentPage 为当前页码,PAGE_SIZE为分页大小
List<Map<String, Object>> _newItems = await DynamicMockData.list(_currentPage, PAGE_SIZE);
任务执行顺序
Dart 基于事件循环(Event Loop),存在一个 事件队列
和一个 微任务队列
,优先执行微任务队列中的事件。
- 所有的外部事件任务都在事件队列中,如 IO、计时器、点击和绘制事件等
- 微任务通常来源于 Dart 内部,任务非常少,太多容易阻塞事件队列
隔离区 Isolate
什么是 Isolate?
- Dart 是单线程的,这个线程有自己可以访问的内存空间及需要运行的事件循环;这样的空间系统称之为一个 Isolate
- Flutter 中有一个 Root Isolate,负责运行 Flutter 的代码,如 UI 渲染、用户交互等
- 每个 Isolate 都有自己的 Event Loop 和 Queue
- Isolate 之间不共享任何资源,只能依靠消息机制通信,因此就没有了资源抢占问题
创建 Isolate
1
2
3
4
5
6
7
8
9
import "dart:isolate";
main(List<String> args) {
Isolate.spawn(foo, "Hello Isolate");
}
void foo(info) {
print("新的isolate:$info");
}
Isolate 通信机制
- Isolate 通过发送管道(
SendPort
)实现消息通信机制
单向通信:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
main(List<String> args) async {
// 1.创建管道
ReceivePort receivePort = ReceivePort();
// 2.创建新的Isolate
Isolate isolate = await Isolate.spawn<SendPort>(foo, receivePort.sendPort);
// 3.监听管道消息
receivePort.listen((data) {
print('Data:$data');
// 不再使用时,我们会关闭管道
receivePort.close();
// 需要将isolate杀死
isolate.kill(priority: Isolate.immediate);
});
}
void foo(SendPort sendPort) {
sendPort.send("Hello World");
}
双向通信:compute
1
2
3
4
5
6
7
8
main(List<String> args) async {
int result = await compute(powerNum, 5);
print(result);
}
int powerNum(int num) {
return num * num;
}
Stream
Stream 也是用于接收异步事件数据,和 Future 不同的是,它可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Stream.fromFutures([
// 1秒后返回结果
Future.delayed(Duration(seconds: 1), () {
return "hello 1";
}),
// 抛出一个异常
Future.delayed(Duration(seconds: 2),(){
throw AssertionError("Error");
}),
// 3秒后返回结果
Future.delayed(Duration(seconds: 3), () {
return "hello 3";
})
]).listen((data){
print(data);
}, onError: (e){
print(e.message);
},onDone: (){
});
本文由作者按照 CC BY 4.0 进行授权