Flutter原理篇-聊一聊future,await,事件队列,微任务

上一篇我们提到了ReactNative的《javaScript promise await 宏任务 微任务》的内容,如果想看的话请点击 《javaScript promise await 宏任务 微任务》,在这一篇的时候我们就提到了要写一篇关于Flutter的future await事件队列, 微任务相关内容的文章,并且两门语言要来做一个对比,其实关于这个话题网络上已经有一些文章写得还不错了,我的这篇文章主要想写一些不一样的东西,把一些网络错误的东西给矫正,把一些没有提及的东西给他细化明确,也给自己做一个自我的总结,如果你还迷惑在这几个概念中的话,如果你还被他们的执行顺序搞得痛苦不堪的话,如果你想痛定思痛搞清楚的话,OK那么你来对地方了,那么我们今天就弄清楚他们几个的原理,那我们现在就开始吧:

首先来看一下Dart的事件队列(Event loop):

Dart的事件循和JavaScript的基本一样。循环中有两个队列。一个是微任务队列(MicroTask queue),一个是事件队列(Event queue)。其中Dart的事件队列对应的是JavaScript的宏队列,Dart的微队列对应的是和JavaScript的微队列

  • 事件队列包含外部事件,例如I/O, Timer,绘制事件,Future(部分情况)等等。
  • 微任务队列则包含有Dart内部的微任务,主要是通过scheduleMicrotask,Future(部分情况)来调度。

再来看一张很熟悉的图片:

Dart的事件循环的运行遵循以下规则:

  1. 首先处理所有微任务队列里的微任务。
  2. 处理完所有微任务以后。从事件队列里取1个事件进行处理。
  3. 回到微任务队列继续循环。

注意第一步里的所有,也就是说在处理事件队列之前,Dart要先把所有的微任务处理完。如果某一时刻微任务队列里有8个微任务,事件队列有2个事件,Dart也会先把这8个微任务全部处理完再从事件队列中取出1个事件处理,之后又会回到微任务队列去看有没有未执行的微任务。(这一点与JavaScript很像,JavaScript也是处理下一个宏任务的时候回把当前的微任务全部都清空处理)

总而言之,就是对微任务队列是一次性全部处理,对于事件队列是一次只处理一个。

那么在Dart中如何让你的代码异步执行呢?很简单,把要异步执行的代码放在微任务队列或者事件队列里就行了。

可以调用scheduleMicrotask来让代码以微任务的方式异步执行

scheduleMicrotask((){
        print('a microtask');
    });

可以调用Timer.run来让代码以Event loop的方式异步执行

Timer.run((){
       print('a event loop’);
   });
上面已经简单的介绍了事件队列与微任务,现在我们介绍下Future,也就是对应JavaScript之中的Promise

Future提供了一系列构造函数供你选择。:

  • 创建一个立刻在事件队列里运行的Future:
Future(() => print('立刻在Event queue中运行的Future'));
  • 创建一个延时1秒在事件队列里运行的Future:
Future.delayed(const Duration(seconds:1), () => print('1秒后在Event queue中运行的Future'));
  • 创建一个在微任务队列里运行的Future:
Future.microtask(() => print('在Microtask queue里运行的Future'));
  • 创建一个同步运行的Future:
Future.sync(() => print('同步运行的Future'));

上面式Future的几种常见的创建方式,我们使用过JavaScript就知道创建了Promise是需要使用then来作为回调函数的处理的,Dart的Future也是一样的用 then来处理回调和链式调用,Dart的then函数也会返回一个Future作为链式调用的条件,这一点与JavaScript的Promise的then极为的相似,例如:

Future(()=> print('task0’))
    .then((_)=> print('callback0’))
    .then((_)=> print('callback1’));
以上就是对Future的一些简单用法的介绍,Future背后的运行机制还是有一些复杂的,如果搞不懂连代码的执行顺序都摸不清楚,这里先列几个来自Dart官网的关于Future的说明:
  • 你通过then串起来的那些回调函数在Future完成的时候会被立即执行,也就是说它们是同步执行,而不是被调度异步执行。

(这种运行情况最普通最常见的then运行方式,但是不包括下面的提到的那种情况)这点对比JavaScript的then是完全不同的,JavaScript里面Promise的then是被调度到微队列执行的,不会马上同步执行

  • 如果Future在调用then串起回调函数之前已经完成,那么这些回调函数会被调度到微任务队列异步执行。

这种情况非常特殊也很常见,这种情况下的then在Dart中不是立马执行的,例如通过Future.value()实例化的Future会被调度到微任务队列异步完成,这种情况就非常类似JavaScript的执行 Promise.resolve(val).then(val => console.log(val)) 的情况,这种情况下JavaScript也是通过resolve立即返回一个fullfilled状态的Promise,而then也是在微队列执行的

  • 通过Future()和Future.delayed()实例化的Future不会同步执行,它们会被调度到事件队列异步执行。
    这种情况依然非常常见,通过构造函数实例化的Future会被调度到 事件队列异步执行 ,这种情况与JavaScript也是完全不同的,JavaScript的Promise的构造函数是在宏任务可以立即执行的(说白了是同步的),例如:
//JavaScript
new Promise(function(resolve) {
    console.log('promise');
    resolve(true)
}).then(function() {
    console.log('then');
})
// promise
// then
  • 通过Future.sync()实例化的Future会同步执行其入参函数,然后(除非这个入参函数返回一个Future)调度到微任务队列来完成自己,类似于第2条。
从上述说明可以得出结论,Future中的代码至少会有一部分被异步调度执行的,要么是其入参函数和回调被异步调度执行,要么就只有回调被异步调度执行,但是不会两部分都异步执行

下面我们来个简单的例子感受下:

import "dart:async";

main(List<String> args) {
  print("main start");

   final future = Future(() => null);
   Future(() => print("task1"));

  Future(() => print("task2")).then((_) {
    print("task3");
    scheduleMicrotask(() => print('task4'));
  }).then((_) => print("task5"));

  future.then((_) => print("task6"));
  scheduleMicrotask(() => print('task7'));
  Future.value(3).then((val) => print('task11'));

  Future(() => print('task8'))
    .then((_) => Future(() => print('task9')))
    .then((_) => print('task10'));

  print("main end");
}

//main start
//main end
//task7
//task11
//task6
//task1
//task2
//task3
//task5
//task4
//task8
//task9
//task10

打印顺序简单的总结下:

  1. main函数先执行,所以main start和main end先执行,没有任何问题;
  2. main函数执行过程中,会将一些任务分别加入到EventQueue和MicrotaskQueue中;
  3. task7通过scheduleMicrotask函数调用,所以它被最早加入到MicrotaskQueue,会被先执行,task11通过Future.value()创建也是会加入到MicrotaskQueue,接着执行
  4. 然后开始执行EventQueue,task6的 final future = Future(() => null) 最早创建,我们知道future构造函数创建会加入到EventQueue队列中(网络上说这个这种情况下是加入到微队列这是不对的,你多做几次试验就知道了,这个肯定是加入到了EventQueue中), 所以打印print("task6”)也会先执行(这个也说明了then串起来的那些回调函数在Future完成的时候会被立即执行,也就是说它们是同步执行)
  5. task1也被添加到EventQueue中被执行;
  6. 一次在EventQueue中添加task2、task3、task5被执行,再执行task4
  7. task3的打印执行完后,调用scheduleMicrotask会在执行完这次的EventQueue后会立即执行,所以在task5后执行task4(这还是因为then串起来的那些回调函数在Future完成的时候会被立即执行
  8. task8、task9、task10一次添加到EventQueue被执行;
好了通过上面的例子我相信你已经对于Dart的Future的执行过程已经理解很深了,那么我们继续看看Dart中async,await的内容:

在Dart1.9中加入了async和await关键字,有了这两个关键字,我们可以更简洁的编写异步代码,而不需要调用Future相关的API。他们允许你像写同步代码一样写异步代码和不需要使用Future接口。相当于都Future相关API接口的另一种封装,提供了一种更加简便的操作Future相关API的方法,这个概念与我们JavaScript里面的async和await关键字实在是像极了,可以说一模一样,JavaScript里面的async和await其实也是对于Promise的封装

Dart将 async 关键字作为方法声明的后缀时,具有如下意义:
  • 被修饰的方法会将一个 Future 对象作为返回值(这个JavaScript中也是一样的概念,JavaScript的async会返回一个Promise)
  • 该方法会同步执行其中的方法的代码直到第一个 await 关键字,然后它暂停该方法其他部分的执行;(这个也与JavaScript中是一样的)
  • 下面说两个细节(网络上很多文章都是点到为止,这两个细节一般不怎么提到):
  1. await后面代码可以是两种情况,一种是最常见的await等待的是一个Future(也就是另一个async函数),那么这种情况会阻塞这个async函数return的那一句话,转而执行await函数外面的代码,这种情况是最为常见的现象(而且这种情况下与JavaScript的await执行情况是一模一样的),那什么时候轮到await函数后面的代码执行呢,其实普通的async函数return后面的代码Dart会把它转变为一个微任务的Future,轮到微任务执行的时间就会开始执行,下面我们看一下代码示例 :
import "dart:async";

foo() async {
  print('foo E');
  String value = await fun();
  print('foo X $value');
}

fun() async{
  print('fun');
  return 'fun';
}

main() {
  print('main E');
  foo();
  Future.value(333).then((a)=> print(a));
  print("main X");
}

//main E
//foo E
//fun
//main X
//foo X fun
//333

执行顺序:

  • 首先执行打印’main E' ;然后进入foo()函数这是一个异步函数,首先打印里面的同步代码foo E;
  • 然后await fun()函数,进入到fun函数打印同步代码’fun’ ,然后return ’fun‘ 字符串,这里会阻塞await后面的代码(后面的代码变成一个微队列任务执行);
  • 然后转而执行async外面的代码 Future.value(333).then((a)=> print(a)); 也加入到微任务执行;
  • 最后面打印’main X‘;然后开始按照顺序 执行微任务,所以会先打印’foo X fun‘,再打印’333‘
我再把 代码稍微改一下再来看看执行效果:
import "dart:async";

foo() async {
  print('foo E');
  String value = await fun();
  print('foo X $value');
}

fun() {
  print('fun');
  return Future(()=>'fun Funture');
}

main() {
  print('main E');
  foo();
  Future.value(333).then((a)=> print(a));
  print("main X");
}

//main E
//foo E
//fun
//main X
//333
//foo X fun Funture

去掉了fun的async关键字,并且改了fun函数里面的return的代码,让他返回一个future,执行顺序为什么变了呢,很简单啊,因为return的Future是通过构造函数实现的,这种Future就是一个EventQueue事件,他的优先级在微队列下面,所以最后执行

下面聊await后面代码的另一种情况:
  1. await后面代码还可以是等待一个表达式,那么这种情况会阻塞await那一句话,转而执行await函数外面的代码,(这种情况下也是与JavaScript的await等待表达式执行情况是一模一样的,我经常怀疑Dart肯定借鉴了很多JavaScript的优秀设计),那什么时候轮到await函数后面的代码执行呢,await后面的代码Dart会把它转变为一个微任务的Future,轮到微任务执行的时间就会开始执行,我们再来看一下代码示例 :
import "dart:async";

foo() async {
  print('foo E');
  String value = await '100';
  print('foo X $value');
}

main() {
  print('main E');
  Future(() => 111).then((val) => print(val));
  Future.value(333).then((a)=> print(a));
  foo();
  print("main X");
}

//main E
//foo E
//main X
//333
//foo X 100
//111

简单的讲解下执行顺序: - 首先打印‘main E’ ,然后要print(111)放入到EventQueue, - 然后遇到print(333)放入微队列, - 最后遇到await 100 表达式,按照上面说的应该放到微队列任务里面,等主线程打印’main X’ 然后执行微队列的任务打印333,然后再打印foo X 100,最后微任务执行完毕再执行EventQueue的任务打印111

好了,现在对于flutter的Future await事件队列, 微任务目前已经讲解完毕了,我们先从异步运行的基础(Event Loop)开始,首先介绍了异步运行机制,到Future;再到 async和await,通过了几个小的例子了解了Flutter/Dart中的异步运行机制背后的原理是什么,对于一直从事Native开发,不太了解JavaScript的同学来讲,这个异步机制和原生开发还是有很大的不同,需要多加的练习以及思考才能 熟练运用,好了如果你喜欢本文的话帮忙点个赞,如果你有任何疑问的话可以给我留言,欢迎一起探讨问题···

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值