与 JavaScript 一样, Dart 是基于
事件循环机制的单线程模型, 所以 Dart 中没有多线程。和浏览器中Javascript表现类似,dart也是通过事件循环(Event Loop)机制来实现异步机制
Event Loop
Dart 在执行完 main 函数后, Event Looper 就开始工作, Event Looper 优先全部执行完 Microtask Queue 中的 event, 直到 Microtask Queue 为空时, 才会执行 Event Looper 中的 event, Event Looper 为空时才可以退出循环.
Microtask Queue
Microtask Queue的优先级高于Event Queue.- 使用场景: 想要在稍后完成一些任务(microtask) 但又希望在执行下一个事件(event)之前执行.
Microtask 一般用于非常短的内部异步动作, 并且任务量非常少, 如果微任务非常多, 就会造成 Event Queue 排不上队, 会阻塞 Event Queue 的执行(如: 用户点击没有反应). 所以, 大多数情况下优先考虑使用 Event Queue, 整个 Flutter 源代码仅引用
scheduleMicroTask()方法 7 次.
将任务添加到MicroTask队列有两种方法 1.使用 scheduleMicrotask 方法添加 2.使用Future对象添加
import 'dart:async';
//我的任务队列
void myTask(){
print("this is my task");
}
void main() {
// 1. 使用 scheduleMicrotask 方法添加
scheduleMicrotask(myTask);
// 2. 使用Future对象添加
Future.microtask(myTask);
}
Event Queue
Event Queue 的 event 来源于 外部事件 和 Future
- 外部事件: 例如输入/输出, 手势, 绘制, 计时器, Stream 等
- Future: 用于自定义 Event Queue 事件
对于外部事件, 一旦没有任何 microtask 要执行, Event loop才会考虑 event queue中的第一项,并且将会执行它.
将任务添加到Event队列
使用Future对象添加
import 'dart:async';
//我的任务
void myTask(){
print("this is my task");
}
void main() {
// 1. 使用Future对象添加
new Future(myTask);
}
运行结果:
main start
main stop
this is microtask
this is my task
可以看到,代码的运行顺序并不是按照我们的编写顺序来的,将任务添加到队列并不等于立刻执行,它们是异步执行的,当前main方法中的代码执行完之后,才会去执行队列中的任务,且MicroTask队列运行在Event队列之前
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 实例, 从而达到链式调用的效果, 这对那些有数据关联的网络请求很有用:
dart
复制代码
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 写出来的代码在理解上会更加清晰。
Stream
Stream 是一系列异步事件的序列。其类似于一个异步的 Iterable,不同的是当你向 Iterable 获取下一个事件时它会立即给你,但是 Stream 则不会立即给你而是在它准备好时告诉你 Stream 有两种类型。
Single-Subscription 类型的 Stream
最常见的类型是一个 Stream 只包含了某个众多事件序列的一个。而这些事件需要按顺序提供并且不能丢失。当你读取一个文件或接收一个网页请求时就需要使用这种类型的 Stream。
这种 Stream 只能设置一次监听。重复设置则会丢失原来的事件,而导致你所监听到的剩余其它事件毫无意义。当你开始监听时,数据将以块的形式提供和获取。
Broadcast 类型的 Stream
另一种流是针对单个消息的,这种流可以一次处理一个消息。例如可以将其用于浏览器的鼠标事件。
你可以在任何时候监听这种 Stream,且在此之后你可以获取到任何触发的事件。这种流可以在同一时间设置多个不同的监听器同时监听,同时你也可以在取消上一个订阅后再次对其发起监听。
创建Stream
单订阅Stream
periodic
void main(){
test();
}
test() async{
// 使用 periodic 创建流,第一个参数为间隔时间,第二个参数为回调函数
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
// await for循环从流中读取
await for(var i in stream){
print(i);
}
}
// 可以在回调函数中对值进行处理,这里直接返回了
int callback(int value){
return value;
}
打印结果:
0
1
2
3
4
...
该方法从整数0开始,在指定的间隔时间内生成一个自然数列,以上设置为每一秒生成一次,callback函数用于对生成的整数进行处理,处理后再放入Stream中。这里并未处理,直接返回了。要注意,这个流是无限的,它没有任何一个约束条件使之停止。在后面会介绍如何给流设置条件。
fromFuture
void main(){
test();
}
test() async{
print("test start");
Future<String> fut = Future((){
return "async task";
});
// 从Future创建Stream
Stream<String> stream = Stream<String>.fromFuture(fut);
await for(var s in stream){
print(s);
}
print("test end");
}
打印结果:
bash
复制代码
test start
async task
test end
该方法从一个Future创建Stream,当Future执行完成时,就会放入Stream中,而后从Stream中将任务完成的结果取出。这种用法,很像异步任务队列。
fromFutures
从多个Future创建Stream,即将一系列的异步任务放入Stream中,每个Future按顺序执行,执行完成后放入Stream
import 'dart:io';
void main() {
test();
}
test() async{
print("test start");
Future<String> fut1 = Future((){
// 模拟耗时5秒
sleep(Duration(seconds:5));
return "async task1";
});
Future<String> fut2 = Future((){
return "async task2";
});
// 将多个Future放入一个列表中,将该列表传入
Stream<String> stream = Stream<String>.fromFutures([fut1,fut2]);
await for(var s in stream){
print(s);
}
print("test end");
}
fromIterable
该方法从一个集合创建Stream,用法与上面例子大致相同
dart
复制代码
// 从一个列表创建`Stream`
Stream<int> stream = Stream<int>.fromIterable([1,2,3]);
value
这是Dart2.5 新增的方法,用于从单个值创建Stream
test() async{
Stream<bool> stream = Stream<bool>.value(false);
// await for循环从流中读取
await for(var i in stream){
print(i);
}
}
通过async*创建
如果有一系列事件需要处理,也许会需要把它转化为 stream。这时候可以使用 async*** 和 yield** 来生成一个 Stream。
void main() {
generateStream(10).listen((event) => print(event),
onDone: () => print('is done'),
onError: (error, stacktrace) => print('is error, errMsg: $error'),
cancelOnError: true);
}
Stream<int> generateStream(int dest) async* {
for (int i = 1; i <= dest; i++) {
yield i;
}
}
广播Stream
有两种方式创建广播流,一种直接从Stream创建,另一种使用StreamController创建
从Stream创建
test() async{
// 调用 Stream 的 asBroadcastStream 方法创建
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (e)=>e)
.asBroadcastStream();
stream = stream.take(5);
stream.listen(print);
stream.listen(print);
}
StreamController创建
test() async{
// 创建广播流
StreamController sc = StreamController.broadcast();
sc.stream.listen(print);
sc.stream.listen(print);
sc.add("event1");
sc.add("event2");
}
监听Stream
async-await配合for或forEach循环处理
通过async-await配合for或forEach可以实现当Stream中每个事件到来的时候处理它,由于Stream接收事件时机是不确定,所以for或forEach循环退出的时候一般是Stream关闭或者完成结束的时候
void main() async {
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (int value) {
return value + 1;
});
await stream.forEach((element) => print('stream value is: $element'));
}
listen 监听
// StreamSubscription<T> listen(void onData(T event), {Function onError, void onDone(), bool cancelOnError})
test() async{
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.take(5);
stream.listen(
(x)=>print(x),
onError: (e)=>print(e),
onDone: ()=>print("onDone"));
}
-
onData: 接收到数据时触发回调
-
onError: 接收到异常时触发回调
-
onDone: 数据接收完毕触发回调
-
cancelOnError: 表示true(出现第一个error就取消订阅,之后事件将无法接收;false表示出现error后,后面事件可以继续接收)
Stream 的一些方法
take 和 takeWhile
Stream<T> take(int count) 用于限制Stream中的元素数量
test() async{
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
// 当放入三个元素后,监听会停止,Stream会关闭
stream = stream.take(3);
await for(var i in stream){
print(i);
}
}
打印结果:
0
1
2
Stream<T>.takeWhile(bool test(T element)) 与 take作用相似,只是它的参数是一个函数类型,且返回值必须是一个bool值
stream = stream.takeWhile((x){
// 对当前元素进行判断,不满足条件则取消监听
return x <= 3;
});
skip 和 skipWhile
test() async{
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.take(5);
// 表示从Stream中跳过两个元素
stream = stream.skip(2);
await for(var i in stream){
print(i);
}
}
打印结果:
2
3
4
请注意,该方法只是从Stream中获取元素时跳过,被跳过的元素依然是被执行了的,所耗费的时间依然存在,其实只是跳过了执行完的结果而已。
Stream<T> skipWhile(bool test(T element)) 方法与takeWhile用法是相同的,传入一个函数对结果进行判断,表示跳过满足条件的。
toList
Future<List<T>> toList() 表示将Stream中所有数据存储在List中
test() async{
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.take(5);
List <int> data = await stream.toList();
for(var i in data){
print(i);
}
}
属性 length
等待并获取流中所有数据的数量
test() async{
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.take(5);
var len = await stream.length;
print(len);
}
StreamController
它实际上就是Stream的一个帮助类,可用于整个 Stream 过程的控制。
import 'dart:async';
void main() {
test();
}
test() async{
// 创建
StreamController streamController = StreamController();
// 放入事件
streamController.add('element_1');
streamController.addError("this is error");
streamController.sink.add('element_2');
streamController.stream.listen(
print,
onError: print,
onDone: ()=>print("onDone"));
}
使用该类时,需要导入'dart:async',其add方法和sink.add方法是相同的,都是用于放入一个元素,addError方法用于产生一个错误,监听方法中的onError可获取错误。
还可以在StreamController中传入一个指定的stream
test() async{
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (e)=>e);
stream = stream.take(5);
StreamController sc = StreamController();
// 将 Stream 传入
sc.addStream(stream);
// 监听
sc.stream.listen(
print,
onDone: ()=>print("onDone"));
}
现在来看一下StreamController的原型,它有5个可选参数
factory StreamController(
{void onListen(),
void onPause(),
void onResume(),
onCancel(),
bool sync: false})
onListen注册监听时回调onPause当流暂停时回调onResume当流恢复时回调onCancel当监听器被取消时回调sync当值为true时表示同步控制器SynchronousStreamController,默认值为false,表示异步控制器
test() async{
// 创建
StreamController sc = StreamController(
onListen: ()=>print("onListen"),
onPause: ()=>print("onPause"),
onResume: ()=>print("onResume"),
onCancel: ()=>print("onCancel"),
sync:false
);
StreamSubscription ss = sc.stream.listen(print);
sc.add('element_1');
// 暂停
ss.pause();
// 恢复
ss.resume();
// 取消
ss.cancel();
// 关闭流
sc.close();
}
打印结果:
onListen
onPause
onCancel
因为监听器被取消了,且关闭了流,导致"element_1"未被输出,"onResume"亦未输出
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);
}
StreamTransformer
该类可以使我们在Stream上执行数据转换。然后,这些转换被推回到流中,以便该流注册的所有监听器可以接收
构造方法原型
factory StreamTransformer.fromHandlers({
void handleData(S data, EventSink<T> sink),
void handleError(Object error, StackTrace stackTrace, EventSink<T> sink),
void handleDone(EventSink<T> sink)
})
handleData:响应从流中发出的任何数据事件。提供的参数是来自发出事件的数据,以及EventSink<T>,表示正在进行此转换的当前流的实例handleError:响应从流中发出的任何错误事件handleDone:当流不再有数据要处理时调用。通常在流的close()方法被调用时回调
void test() {
StreamController sc = StreamController<int>();
// 创建 StreamTransformer对象
StreamTransformer stf = StreamTransformer<int, double>.fromHandlers(
handleData: (int data, EventSink sink) {
// 操作数据后,转换为 double 类型
sink.add((data * 2).toDouble());
},
handleError: (error, stacktrace, sink) {
sink.addError('wrong: $error');
},
handleDone: (sink) {
sink.close();
},
);
// 调用流的transform方法,传入转换对象
Stream stream = sc.stream.transform(stf);
stream.listen(print);
// 添加数据,这里的类型是int
sc.add(1);
sc.add(2);
sc.add(3);
// 调用后,触发handleDone回调
// sc.close();
}
打印结果:
2.0
4.0
6.0
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() 函数. 以下是示例代码:
dart
复制代码
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 项目中才能运行.