likes
comments
collection
share

Flutter中的异步执行策略

作者站长头像
站长
· 阅读数 10

在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循环的一次迭代中被执行。 Flutter中的异步执行策略 可以通过这个链接查看更多内容: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");
});

它的内部实现如下。 Flutter中的异步执行策略

Microtasks

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

scheduleMicrotask

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

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

Future.microtask

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

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

它的内部实现如下。 Flutter中的异步执行策略

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…

欢迎大家关注我的公众号——【群英传】,专注于「Android」「Flutter」「Kotlin」 我的语雀知识库——www.yuque.com/xuyisheng