注册

Flutter中的异步执行策略

在Flutter中,如何执行一段延迟执行的异步代码?我们可以找到下面这些方法。



  • scheduleMicrotask
  • Future.microtask
  • Future
  • Future.delayed
  • Timer.run
  • WidgetsBinding.addPostFrameCallback
  • SchedulerBinding.addPostFrameCallback

你可能会说,这是相当多的选择,但是它们彼此之间有些什么异同呢?


Event Loop and Multithreading


Dart是一个单线程模型。但是你的Flutter应用同样可以同时做多件事情,这就是「Event Loop」发挥作用的地方。Event Loop是一个无尽的循环,它执行预定的events。这些events(或者只是代码块)必须是轻量级的,否则,你的应用程序会感觉卡顿。


每个event,如按下按钮或网络请求,都被安排在一个事件队列中,等待被事件循环捡起并执行。这种设计模式在UI和其他处理任何类型事件的系统中相当常见。


在Dart的单线程模型中,还有一个Microtask。它组成了Event Loop中的另一一个队列,即Microtask Queue。关于这个队列你唯一需要记住的是,在事件本身被执行之前,所有安排在Microtask Queue的任务都将在Event Loop循环的一次迭代中被执行。
image.png
可以通过这个链接查看更多内容:dart.cn/articles/ar…


Events


任何进入event queue的东西都被称之为Event。这是Flutter中调度异步任务的默认方法。为了调度一个Event,我们把它添加到event queue中,由Event Loop来接收。这种方法被许多Flutter机制所使用,如I/O、手势事件、Timer等。


Timer


Timer是Flutter中异步任务的基础。它被用来安排event queue中的代码执行,无论是否有延迟执行的需要。由此产生的有趣的事实是,如果当前队列很忙,你的定时器将永远不会被执行,即使时间到了。


Timer.run(() {
print("Timer");
});


Future and Future.delayed


Future是Dart中使用的非常广泛的一个异步方法,它的内部实现,实际上也就是基于Timer的。


Future<void>(() {
print("Future Event");
});

Future<void>.delayed(Duration.zero, () {
print("Future.delayed Event");
});

它的内部实现如下。
image.png


Microtasks


如前所述,所有调度的microtasks都会在下一个调度的Event之前执行。建议避免使用这个队列,除非绝对需要异步执行代码,而且要在event queue的下一个事件之前处理。你也可以把这个队列看成是属于前一个事件的任务队列,因为它们将在下一个事件之前完成。如果这个队列不断膨胀,就会完全冻结你的应用程序,因为它必须先执行这个队列中的所有内容,然后才能进行其事件队列的下一次迭代,例如处理用户输入,甚至渲染应用程序本身。


scheduleMicrotask


顾名思义,在microtask queue中调度一个块代码。与Timer类似,如果出错,会使应用程序崩溃。


scheduleMicrotask(() {
print("Microtask");
});


Future.microtask


与我们之前看到的类似,但它将我们的microtask包裹在一个try-catch块中,以一种漂亮而干净的方式返回执行结果或异常。


Future<void>.microtask(() {
print("Microtask");
});
复制代码

它的内部实现如下。
image.png


Post Frame Callback


前面两种方法只涉及到lower-level Event Loop,而现在我们要转到Flutter领域。这个Callback会在渲染管道完成时被调用,所以它与widget的生命周期相管理。当它被调度时,它只会被调用一次,而不是在每一帧都回调。使用addPostFrameCallback方法,你可以安排一个或多个回调,在界面渲染完成后被调用。


所有预定的Callback将在frame结束时按照它们被添加的顺序执行。到这个回调被调用的时候,可以保证Widget的构建过程已经完成。通过一些方法,你甚至可以访问Widget(RenderBox)的布局信息,比如它的大小,并做其他的一些事情。Callback本身将在正常的event queue中运行,Flutter默认使用该队列来处理几乎所有事情。


SchedulerBinding


这是一个负责绘图回调的mixin类,实现了我们感兴趣的方法。


SchedulerBinding.instance.addPostFrameCallback((_) {
print("SchedulerBinding");
});


WidgetsBinding


我特意包括这个,因为它经常和SchedulerBinding一起被提及。它从SchedulerBinding中继承了这个方法,并有与我们的主题无关的一些额外方法。一般来说,你使用SchedulerBinding或WidgetsBinding并不重要,两者将执行位于SchedulerBinding中的完全相同的代码。


WidgetsBinding.instance.addPostFrameCallback((_) {
print("WidgetsBinding");
});


总结


由于我们今天学到了很多理论知识,我强烈建议大家多玩一会儿,以确保我们能正确地掌握它。我们可以在之前的initState中使用下面的代码,并尝试预测它将以何种顺序被执行,这并不是一件看起来很容易的事情。


SchedulerBinding.instance.addPostFrameCallback((_) {
print("SchedulerBinding");
});

WidgetsBinding.instance.addPostFrameCallback((_) {
print("WidgetsBinding");
});

Timer.run(() {
print("Timer");
});

scheduleMicrotask(() {
print("scheduleMicrotask");
});

Future<void>.microtask(() {
print("Future Microtask");
});

Future<void>(() {
print("Future");

Future<void>.microtask(() {
print("Microtask from Event");
});
});

Future<void>.delayed(Duration.zero, () {
print("Future.delayed");

Future<void>.microtask(() {
print("Microtask from Future.delayed");
});
});

输出结果如下所示。


I/flutter (31989): scheduleMicrotask
I/flutter (31989): Future Microtask
I/flutter (31989): SchedulerBinding
I/flutter (31989): WidgetsBinding
I/flutter (31989): Timer
I/flutter (31989): Future
I/flutter (31989): Microtask from Event
I/flutter (31989): Future.delayed
I/flutter (31989): Microtask from Future.delayed

现在我们了解了这么多细节,你可以对如何安排你的代码做出深思熟虑的决定。作为一个经验法则,如果你需要你的上下文或与Layout或UI相关的东西,请使用addPostFrameCallback。在任何其他情况下,用Future或Future.delayed在标准的event queue中进行调度应该是足够的。microtask queue是非常小众的东西,你可能永远不会遇到,但它仍然值得了解。当然,如果你有一个繁重的任务,你就会考虑创建一个Isolate。


翻译自——oleksandrkirichenko.com/blog/delaye…


作者:xuyisheng
链接:https://juejin.cn/post/7208222652619948069
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册