和谐学习!不急不躁!!我是你们的老朋友小青龙~
前言
Dart是单线程语言,它有两种队列:事件队列、微任务队列。
-
事件队列
:常见的情况就是绘制事件、鼠标事件、文件流读写事件、计时、Dart isolate之间的消息通讯等所有外来事件。 -
微任务队列
:表示需要短时间内快速处理的异步任务,它的优先级高于事件队列。
什么是异步任务?
假设有任务A、任务B、任务C,任务B需要消耗很长一段时间才能完成。
但是任务B的执行不会卡住任务C的执行,我们称任务B是`异步任务`。
异步任务有哪些方式?
-
Future
-
scheduleMicrotask
正文
Future
在开始正文内容之前,我们先了解一个API - - - Future
关于Future函数,文档的解释如下:
/// Creates a future containing the result of calling [computation]
/// asynchronously with [Timer.run].
百度翻译:创建包含调用[computation(计算)]结果的future(未来),与[Timer.run]异步
所以,Future函数包含的代码块是「异步执行」的。
1、非异步任务
假设我们有任务A、任务B(耗时任务)、任务C,正常情况下是这样的:
void main() {
print('任务 A');
getData();
print('任务 C');
}
getData() {
print('任务 B');
for (int i = 0; i < 10; i++) {
print(i);
}
print('for循环打印结束');
}
运行效果:
任务B会堵塞当前任务,依次执行任务A、任务B、任务C。
2.1、异步任务
需求
:假如我希望任务B不堵塞当前线程。
方案
:把耗时任务放到Future
函数里即可。
void main() {
print('任务 A');
getData();
print('任务 C');
}
getData() {
Future(() {
print('任务 B');
for (int i = 0; i < 10; i++) {
print(i);
}
print('for循环打印结束');
});
}
运行效果:
当然异步除了「Future」还可以用「scheduleMicrotask」实现,这个后面会讲到。
2.2、异步任务之 async + await Future
需求
:假如我希望print('for循环打印结束');
这句代码写到Future外面呢?
方案
:可以利用 async + await Future
组合来实现。
getData() async {
await Future(() {
print('任务 B,async + await Future');
for (int i = 0; i < 10; i++) {
print(i);
}
});
print('for循环打印结束');
}
运行效果:
注意:
-
await不能单独使用,必须结合async来使用。
(即await必须在异步函数里使用) -
注意:await属于
任务阻塞
,不是线程阻塞
。
异步任务 - 使用场景
需求
:需要查询个人账户变动信息
要调用查询个人账户变动信息接口
,其中一个很关键的参数是身份识别码
,而身份识别码是在个人信息接口
里才返回的。
所以getData函数里包含两个异步请求:
-
个人信息接口
-
个人账户变动信息
实现方式一:async + await Future
getData() async {
await Future(() {
print('任务 B,async + await Future');
for (int i = 0; i < 10; i++) {
print(i);
}
print('个人信息接口结束');
});
for (int i = 100; i < 110; i++) {
print(i);
}
print('个人账户变动信息结束');
}
运行效果:
实现方式二:每个请求分别用Future包裹
getData() {
Future(() {
print('任务 B,每个请求分别用Future包裹');
for (int i = 0; i < 10; i++) {
print(i);
}
print('个人信息接口结束');
});
Future(() {
for (int i = 100; i < 110; i++) {
print(i);
}
print('个人账户变动信息结束');
});
}
运行效果:
实现方式三:Future点语法then
getData() {
Future(() {
print('任务 B,Future点语法then');
for (int i = 0; i < 10; i++) {
print(i);
}
print('个人信息接口结束');
return 'xdxxd13567';
}).then((value) {
print('身份识别码是:$value');
for (int i = 100; i < 110; i++) {
print(i);
}
print('个人账户变动信息结束');
});
}
运行效果:
Future点语法then的多样玩法
查看Future函数内部设计
「command + 单机」 进入Future文档页面
,可以看到Future内部是这样设计的:
factory Future(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
Timer.run(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
- 首先它是一个工厂(factory)方法
- 参数是一个回调方法
- 返回值result是Future类型
查看then函数内部设计
then函数
的内部设计如下:
Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError});
- then同样也是返回一个Future类型
怎么玩
利用Future函数和then函数都会返回Future
的这个特性,我们可以实现任务叠加的功能。即:
/// Future点语法then的多样玩法
getData() {
Future _f = Future(() {
print('任务 B,Future点语法then的多样玩法');
for (int i = 0; i < 10; i++) {
print(i);
}
print('个人信息接口结束');
return 'xdxxd13567';
}).then((value) {
print('身份识别码是:$value');
for (int i = 100; i < 110; i++) {
print(i);
}
print('个人账户变动信息结束');
});
_f.then((value) {
print('任务D 开始执行');
}).then((value) {
print('任务F 开始执行');
});
_f.then((value) {
print('任务I 开始执行');
});
}
可以在原来的then基础上继续.then,也可以单独定义Future对象来接收前面任务结束的结果。
实现效果:
多个异步任务,其中一个优先执行(scheduleMicrotask
)
scheduleMicrotask
需求
:假如我有多个异步下载任务,其中一个任务需要优先执行。
方案
:scheduleMicrotask
/// 任务优先执行
getData() {
print('getData 开始执行');
Future(() {
print('任务 1 开始执行');
return '任务1';
}).then((value) {
sleep(const Duration(seconds: 2));
print('任务 2 开始执行');
return '任务2';
});
print('任务 C');
sleep(const Duration(seconds: 2));
scheduleMicrotask(() {
print('任务 X');
});
}
运行效果:
多个scheduleMicrotask,又是如何运行的呢?
/// 任务优先执行
getData() {
print('getData 开始执行');
Future(() {
print('任务 1 开始执行');
return '任务1';
}).then((value) {
sleep(const Duration(seconds: 2));
print('任务 2 开始执行');
return '任务2';
});
print('任务 C');
sleep(const Duration(seconds: 2));
scheduleMicrotask(() {
print('任务 X');
});
scheduleMicrotask(() {
print('任务 Y');
});
scheduleMicrotask(() {
print('任务 Q');
});
}
经测试,XYQ任务执行顺序:任务 X -> 任务 Y -> 任务 Q
即:多个scheduleMicrotask,是同步执行的。
Future.then里包含scheduleMicrotask
/// Future.then 里包含scheduleMicrotask
getData() {
print('getData 开始执行');
Future(() {
print('任务 1');
}).then((value) {
sleep(const Duration(seconds: 2));
// 这里添加一个scheduleMicrotask任务
scheduleMicrotask(() {
print('任务2');
});
print('任务3');
}).then((value) {
print('任务4');
});
print('任务5');
sleep(const Duration(seconds: 2));
scheduleMicrotask(() {
print('任务6');
});
}
小伙伴们,你们猜猜看打印顺序会是什么呢?
运行效果:
打印任务2的scheduleMicrotask居然是最后执行的,这是为什么呢?
原因是:Dart属于单线程语言,当它在执行Dart的函数时候,不会被Dart的其它函数所影响所打断。
首先,scheduleMicrotask属于Microtask(微任务),它优先级高于Future任务,所以「任务6」比「任务1」优先打印;
其次,从代码上来看:「任务2」对应的scheduleMicrotask
任务是在Future的then
里添加的,而「任务3」和「任务4」所代表的then,都是和前面的then紧密相连,它们是一个整体,所以后来添加的scheduleMicrotask最后执行,即「任务2」最后才打印
。
总结
无论是事件队列
还是微任务队列
,都遵循3个原则
:
-
【微任务事件】级别高于【事件队列】
-
对于队列里添加新队列,新队列要在原先队列执行完之后才执行
-
对于同一类型的队列任务,先进先出
案例一:
先后添加【事件队列1】、【事件队列2】,【事件队列1】里面添加了一个【微任务事件1】,
`执行顺序`是【事件队列1】-> 【微任务事件1】-> 【事件队列2】(遵循原则1、2、3)
案例二:
先后添加【事件队列1】、【事件队列2】,【事件队列1】里面添加了一个【事件队列3】,
`执行顺序`是【事件队列1】-> 【事件队列2】-> 【事件队列3】(遵循原则2、3)
案例三:
先后添加【事件队列1】、【事件队列2】,【事件队列1】里面添加了一个【事件队列3】和一个【微任务事件1】,
`执行顺序`是【事件队列1】-> 【微任务事件1】-> 【事件队列2】-> 【事件队列3】(遵循原则1、2、3)
多线程Isolate
我们知道Dart是单线程语言,但有时候难免会需要用到多线程的功能,为解决这个困扰,Dart提供了Isolate
来替代多线程
。
Isolate
有【隔离】的意思,即多个Isolate之间不共享内存
,每个Isolate有各自的存储空间,也就是说Dart的并发
实际上是通过运行多个Isolate
产生的结果。
Isolate初体验
来段代码体验下:
int a = 10;
getData() async {
a++;
print('line230 = $a');
sleep(const Duration(seconds: 2));
Isolate.spawn(func, 'message');
sleep(const Duration(seconds: 2));
print('line234 = $a');
}
void func(String message) {
print('line238 --- $a');
a = a + 3;
}
运行效果:
由此可见,Isolate
里拿到的数据确实不受外部Dart代码影响。
Isolate修改外部值
前面,我们得知了Isolate
的内存是独立的、不受外部影响的,这也导致了Isolate
里面修改的内容,不能及时同步到外面。那么有什么办法可以解决这个问题呢?还真有,贴心的Dart为我们提供了ReceivePort
。
上代码:
int a = 10;
getData() async {
a++;
print('line246 = $a');
ReceivePort port = ReceivePort();
sleep(const Duration(seconds: 2));
Isolate sort = await Isolate.spawn(func, port.sendPort);
port.listen((message) {
print('line251 = $a');
a = message;
print('line253 = $a');
});
sleep(const Duration(seconds: 2));
print('line257 = $a');
port.close();// 关闭窗口
sort.kill();// 回收Isolate内存
}
void func(SendPort sendPort) {
print('line261 --- $a');
a = a + 3;
sendPort.send(a);
}
- line251打印,证明a的确是外部的,不再属于Isolate内部的;
- line253打印,证明的确可以获得Isolate里面的内容。
最后记得关闭窗口、回收Isolate开辟的内存
compute初体验
compute是Isolate的上层封装,所以可以直接用computeResult接收数据
import 'dart:io';
import 'package:flutter/foundation.dart';
void main() {
getData();
}
/// compute初体验
int a = 10;
getData() async {
a++;
print('line16 = $a');
int computeResult = await compute(func2, '20');
sleep(const Duration(seconds: 2));
print('line19 = $a');
print('line20 = $computeResult');
}
int func2(String message) {
print('line24 --- $a');
a = a + 3;
return a;
}
运行效果:
因为加了await,所以后面的代码要等前面执行完了才能继续执行。
compute和Future结合使用
有这么两段代码
- 代码一
void main() => getData();
/// compute和Future结合使用
getData(){
Future((){
compute(func3,'20');
}).then((value) { print('任务A结束'); });
Future((){
compute(func3,'30');
}).then((value) { print('任务B结束'); });
Future((){
compute(func3,'40');
}).then((value) { print('任务C结束');});
Future((){
compute(func3,'50');
}).then((value) { print('任务D结束');});
}
void func3(String char){}
- 代码二
void main() => getData();
getData(){
Future((){
return compute(func3,'20');
}).then((value) { print('任务A结束'); });
Future((){
return compute(func3,'30');
}).then((value) { print('任务B结束'); });
Future((){
return compute(func3,'40');
}).then((value) { print('任务C结束');});
Future((){
return compute(func3,'50');
}).then((value) { print('任务D结束');});
}
void func3(String char){}
执行的结果是
- 任务一固定顺序打印:任务A结束、任务B结束、任务C结束、任务D结束
- 任务二的打印是无需的
我们知道,多个Future任务的时候,Future之间是同步的,为什么到了任务二就变成异步打印了呢?
原因在于
:Future里的return,会把当前函数的结果传给于then,而compute会创建一个子线程,所以then的打印实际上也在子线程,不同的子线程打印当然是无序的。