Dart 中的事件循环模型
下面是 event loop 大致的运行图:
消息队列采用先进先出,下面引用了几张 Dart
开发者网站的介绍:
将消息换成具体的类型后,类似下图:
这个很好理解,事件 events 加到 Event queue 里,Event loop 循环从 Event queue 里取 Event 执行。
Dart 是一种单线程模型运行语言,其运行原理如下图所示:
Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。 从图中可以发现,微任务队列的执行优先级高于事件队列。
现在我们来介绍一下Dart线程运行过程,如上图中所示,入口函数 main() 执行完后,消息循环机制便启动了。 首先会按照先进先出的顺序逐个执行微任务队列中的任务,当所有微任务队列执行完后便开始执行事件队列中的任务,事件任务执行完毕后再去执行微任务, 如此循环往复,生生不息。
在Dart中,所有的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等,而微任务通常来源于Dart内部,并且微任务非常少, 之所以如此,是因为微任务队列优先级高,如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久, 对于GUI应用来说最直观的表现就是比较卡,所以必须得保证微任务队列不会太长。 在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了, 也就是说一个任务中的异常是不会影响其它任务执行的。
我们可以看出,将任务加入到MicroTask中可以被尽快执行,但也需要注意,当事件循环在处理MicroTask队列时,Event队列会被卡住,应用程序无法处理鼠标单击、I/O消息等等事件。 同时,当事件循坏出现异常时,dart 中也可以通过 try/catch/finally 来捕获异常。
任务调度
MicroTask 任务队列
添加到 MicroTask 任务队列两种方法:
import 'dart:async'; void myTask(){ print("this is my task"); } void main() { /// 1. 使用 scheduleMicrotask 方法添加 scheduleMicrotask(myTask); /// 2. 使用Future对象添加 Future.microtask(myTask); }
两个 task 都会被执行,控制台输出:
this is my task this is my task
Event 任务队列
import 'dart:async'; void myTask(){ print("this is my event task"); } void main() { Future(myTask); }
控制台输出:
this is my event task
示例:
import 'dart:async'; void main() { print("main start"); Future((){ print("this is my task"); }); Future.microtask((){ print("this is microtask"); }); print("main stop"); }
控制台输出:
main start main stop this is microtask this is my task
可以看到,代码的运行顺序并不是按照我们的编写顺序来的,将任务添加到队列并不等于立刻执行,它们是异步执行的,当前main方法中的代码执行完之后,才会去执行队列中的任务,且MicroTask队列运行在Event队列之前。
延时任务
Future.delayed(new Duration(seconds:1),(){ print('task delayed'); });
使用 Future.delayed 可以使用延时任务,但是延时任务不一定准
import 'dart:async'; import 'dart:io'; void main() { var now = DateTime.now(); print("程序运行开始时间:" + now.toString()); Future.delayed(Duration(seconds:1),(){ now = DateTime.now(); print('延时任务执行时间:' + now.toString()); }); Future((){ // 模拟耗时5秒 sleep(Duration(seconds:5)); now = DateTime.now(); print("5s 延时任务执行时间:" + now.toString()); }); now = DateTime.now(); print("程序结束执行时间:" + now.toString()); }
输出结果:
程序运行开始时间:2019-09-11 20:46:18.321738 程序结束执行时间:2019-09-11 20:46:18.329178 5s 延时任务执行时间:2019-09-11 20:46:23.330951 延时任务执行时间:2019-09-11 20:46:23.345323 复制代码
可以看到,5s 的延时任务,确实实在当前程序运行之后的 5s 后得到了执行,但是另一个延时任务,是在当前延时的基础上又延时执行的, 也就是,延时任务必须等前面的耗时任务执行完,才得到执行,这将导致延时任务延时并不准。
Future 与 FutureBuilder
Future 的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。
Future 使用
Future 的几种创建方法:
- Future() 默认异步方法
- Future.microtask() 微任务队列
- Future.sync() 同步方法
- Future.value() 返回指定 value 值
- Future.delayed() 延时
- Future.error() 错误
then 回调、 catchError 捕获异常和 whenComplete 一定执行
import 'dart:async'; void main() { print("main start"); Future.delayed(new Duration(seconds: 2),(){ //return "hi world!"; throw AssertionError("Error"); }).then((data){ //执行成功会走到这里 print("success"); }).catchError((e){ //执行失败会走到这里 print(e); }).whenComplete((){ //无论成功或失败都会走到这里 print("this is the end..."); }); print("main stop"); }
输出:
main start main stop Assertion failed
在本示例中,我们在异步任务中抛出了一个异常,then 的回调函数将不会被执行,取而代之的是 catchError 回调函数将被调用;但是,并不是只有 catchError 回调才能捕获错误,then 方法还有一个可选参数 onError,我们也可以它来捕获异常:
import 'dart:async'; void main() { print("main start2"); Future.delayed(new Duration(seconds: 2), () { //return "hi world!"; throw AssertionError("Error"); }).then((data) { print("success"); }, onError: (e) { print(e); }); print("main stop2"); }
结果:
main start2 main stop2 Assertion failed
wait 完成后回调Future.wait 表示多个任务都完成之后的回调。
import 'dart:async'; void main() { print("main start4"); Future.wait([ // 2秒后返回结果 Future.delayed(new Duration(seconds: 2), () { return "hello"; }), // 4秒后返回结果 Future.delayed(new Duration(seconds: 4), () { return " world"; }) ]).then((results){ print(results[0]+results[1]); }).catchError((e){ print(e); }); print("main stop4"); }
结果:
main start4 main stop4 hello world