Flutter 单线程模型保证UI运行流畅

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

完整开源地址:https://docs.qq.com/doc/DSkNLaERkbnFoS0ZF

所以,Event Loop 完整版的流程图,应该如下所示:

接下来,我们分别看一下这两个队列的特点和使用场景吧。

首先,我们看看微任务队列。微任务顾名思义,表示一个短时间内就会完成的异步任务。从上面的流程图可以看到,微任务队列在事件循环中的优先级是最高的,只要队列中还有任务,就可以一直霸占着事件循环。

微任务是由 scheduleMicroTask 建立的。如下所示,这段代码会在下一个事件循环中输出一段字符串:

scheduleMicrotask(() => print(‘This is a microtask’));

不过,一般的异步任务通常也很少必须要在事件队列前完成,所以也不需要太高的优先级,因此我们通常很少会直接用到微任务队列,就连 Flutter 内部,也只有 7 处用到了而已(比如,手势识别、文本输入、滚动视图、保存页面效果等需要高优执行任务的场景)。

异步任务我们用的最多的还是优先级更低的 Event Queue。比如,I/O、绘制、定时器这些异步事件,都是通过事件队列驱动主线程执行的。

Dart 为 Event Queue 的任务建立提供了一层封装,叫作 Future。从名字上也很容易理解,它表示一个在未来时间才会完成的任务。

把一个函数体放入 Future,就完成了从同步任务到异步任务的包装。Future 还提供了链式调用的能力,可以在异步任务执行完毕后依次执行链路上的其他函数体。

接下来,我们看一个具体的代码示例:分别声明两个异步任务,在下一个事件循环中输出一段字符串。其中第二个任务执行完毕之后,还会继续输出另外两段字符串:

Future(() => print(‘Running in Future 1’));// 下一个事件循环输出字符串

Future(() => print(‘Running in Future 2’))

.then((_) => print(‘and then 1’))

.then((_) => print('and then 2’));// 上一个事件循环结束后,连续输出三段字符串

当然,这两个 Future 异步任务的执行优先级比微任务的优先级要低。

正常情况下,一个 Future 异步任务的执行是相对简单的:在我们声明一个 Future 时,Dart 会将异步任务的函数执行体放入事件队列,然后立即返回,后续的代码继续同步执行。而当同步执行的代码执行完毕后,事件队列会按照加入事件队列的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的 then。

这意味着,then 与 Future 函数体共用一个事件循环。而如果 Future 有多个 then,它们也会按照链式调用的先后顺序同步执行,同样也会共用一个事件循环。

如果 Future 执行体已经执行完毕了,但你又拿着这个 Future 的引用,往里面加了一个 then 方法体,这时 Dart 会如何处理呢?面对这种情况,Dart 会将后续加入的 then 方法体放入微任务队列,尽快执行。

下面的代码演示了 Future 的执行规则,即,先加入事件队列,或者先声明的任务先执行;then 在 Future 结束后立即执行。

  • 在第一个例子中,由于 f1 比 f2 先声明,因此会被先加入事件队列,所以 f1 比 f2 先执行;

  • 在第二个例子中,由于 Future 函数体与 then 共用一个事件循环,因此 f3 执行后会立刻同步执行 then 3;

  • 最后一个例子中,Future 函数体是 null,这意味着它不需要也没有事件循环,因此后续的 then 也无法与它共享。在这种场景下,Dart 会把后续的 then 放入微任务队列,在下一次事件循环中执行。

//f1 比 f2 先执行

Future(() => print(‘f1’));

Future(() => print(‘f2’));

//f3 执行后会立刻同步执行 then 3

Future(() => print(‘f3’)).then((_) => print(‘then 3’));

//then 4 会加入微任务队列,尽快执行

Future(() => null).then((_) => print(‘then 4’));

说了这么多规则,可能大家并没有完全记住。那我们通过一个综合案例,来把之前介绍的各个执行规则都串起来,再集中学习一下。

在下面的例子中,我们依次声明了若干个异步任务 Future,以及微任务。在其中的一些 Future 内部,我们又内嵌了 Future 与 microtask 的声明:

Future(() => print(‘f1’));// 声明一个匿名 Future

Future fx = Future(() => null);// 声明 Future fx,其执行体为 null

// 声明一个匿名 Future,并注册了两个 then。在第一个 then 回调里启动了一个微任务

Future(() => print(‘f2’)).then((_) {

print(‘f3’);

scheduleMicrotask(() => print(‘f4’));

}).then((_) => print(‘f5’));

// 声明了一个匿名 Future,并注册了两个 then。第一个 then 是一个 Future

Future(() => print(‘f6’))

.then((_) => Future(() => print(‘f7’)))

.then((_) => print(‘f8’));

// 声明了一个匿名 Future

Future(() => print(‘f9’));

// 往执行体为 null 的 fx 注册了了一个 then

fx.then((_) => print(‘f10’));

// 启动一个微任务

scheduleMicrotask(() => print(‘f11’));

print(‘f12’);

运行一下,上述各个异步任务会依次打印其内部执行结果:

f12

f11

f1

f10

f2

f3

f5

f4

f6

f9

f7

f8

看到这儿,你可能已经懵了。别急,我们先来看一下这段代码执行过程中,Event Queue 与 Microtask Queue 中的变化情况,依次分析一下它们的执行顺序为什么会是这样的:

  • 因为其他语句都是异步任务,所以先打印 f12。

  • 剩下的异步任务中,微任务队列优先级最高,因此随后打印 f11;然后按照 Future 声明的先后顺序,打印 f1。

  • 随后到了 fx,由于 fx 的执行体是 null,相当于执行完毕了,Dart 将 fx 的 then 放入微任务队列,由于微任务队列的优先级最高,因此 fx 的 then 还是会最先执行,打印 f10。

  • 然后到了 fx 下面的 f2,打印 f2,然后执行 then,打印 f3。f4 是一个微任务,要到下一个事件循环才执行,因此后续的 then 继续同步执行,打印 f5。本次事件循环结束,下一个事件循环取出 f4 这个微任务,打印 f4。

  • 然后到了 f2 下面的 f6,打印 f6,然后执行 then。这里需要注意的是,这个 then 是一个 Future 异步任务,因此这个 then,以及后续的 then 都被放入到事件队列中了。

  • f6 下面还有 f9,打印 f9。

  • 最后一个事件循环,打印 f7,以及后续的 f8。

上面的代码很是烧脑,万幸我们平时开发 Flutter 时一般不会遇到这样奇葩的写法,所以你大可放心。你只需要记住一点:then 会在 Future 函数体执行完毕后立刻执行,无论是共用同一个事件循环还是进入下一个微任务。

在深入理解 Future 异步任务的执行规则之后,我们再来看看怎么封装一个异步函数。

异步函数


对于一个异步函数来说,其返回时内部执行动作并未结束,因此需要返回一个 Future 对象,供调用者使用。调用者根据 Future 对象,来决定:是在这个 Future 对象上注册一个 then,等 Future 的执行体结束了以后再进行异步处理;还是一直同步等待 Future 执行体结束。

对于异步函数返回的 Future 对象,如果调用者决定同步等待,则需要在调用处使用 await 关键字,并且在调用处的函数体使用 async 关键字。

在下面的例子中,异步方法延迟 3 秒返回了一个 Hello 2019,在调用处我们使用 await 进行持续等待,等它返回了再打印:

// 声明了一个延迟 3 秒返回 Hello 的 Future,并注册了一个 then 返回拼接后的 Hello 2019

Future fetchContent() =>

Future.delayed(Duration(seconds:3), () => “Hello”)

.then((x) => “$x 2019”);

main() async{

print(await fetchContent());// 等待 Hello 2019 的返回

}

也许你已经注意到了,我们在使用 await 进行等待的时候,在等待语句的调用上下文函数 main 加上了 async 关键字。为什么要加这个关键字呢?

因为Dart 中的 await 并不是阻塞等待,而是异步等待。Dart 会将调用体的函数也视作异步函数,将等待语句的上下文放入 Event Queue 中,一旦有了结果,Event Loop 就会把它从 Event Queue 中取出,等待代码继续执行。

接下来,为了帮助你加深印象,我准备了两个具体的案例。

我们先来看下这段代码。第二行的 then 执行体 f2 是一个 Future,为了等它完成再进行下一步操作,我们使用了 await,期望打印结果为 f1、f2、f3、f4:

Future(() => print(‘f1’))

.then((_) async => await Future(() => print(‘f2’)))

.then((_) => print(‘f3’));

Future(() => print(‘f4’));

实际上,当你运行这段代码时就会发现,打印出来的结果其实是 f1、f4、f2、f3!

我来给你分析一下这段代码的执行顺序:

  • 按照任务的声明顺序,f1 和 f4 被先后加入事件队列。

  • f1 被取出并打印;然后到了 then。then 的执行体是个 future f2,于是放入 Event Queue。然后把 await 也放到 Event Queue 里。

  • 这个时候要注意了,Event Queue 里面还有一个 f4,我们的 await 并不能阻塞 f4 的执行。因此,Event Loop 先取出 f4,打印 f4;然后才能取出并打印 f2,最后把等待的 await 取出,开始执行后面的 f3。

由于 await 是采用事件队列的机制实现等待行为的,所以比它先在事件队列中的 f4 并不会被它阻塞。

接下来,我们再看另一个例子:在主函数调用一个异步函数去打印一段话,而在这个异步函数中,我们使用 await 与 async 同步等待了另一个异步函数返回字符串:

// 声明了一个延迟 2 秒返回 Hello 的 Future,并注册了一个 then 返回拼接后的 Hello 2019

Future fetchContent() =>

Future.delayed(Duration(seconds:2), () => “Hello”)

.then((x) => “$x 2019”);

// 异步函数会同步等待 Hello 2019 的返回,并打印

func() async => print(await fetchContent());

main() {

print(“func before”);

func();

print(“func after”);

}

运行这段代码,我们发现最终输出的顺序其实是“func before”“func after”“Hello 2019”。func 函数中的等待语句似乎没起作用。这是为什么呢?

同样,我来给你分析一下这段代码的执行顺序:

  • 首先,第一句代码是同步的,因此先打印“func before”。

  • 然后,进入 func 函数,func 函数调用了异步函数 fetchContent,并使用 await 进行等待,因此我们把 fetchContent、await 语句的上下文函数 func 先后放入事件队列。

  • await 的上下文函数并不包含调用栈,因此 func 后续代码继续执行,打印“func after”。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值