最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • [Flutter翻译]Flutter中延迟的代码执行

    正文概述 掘金(Sunbreak)   2021-02-22   486

    Microtask,Future还是postFrameCallback,我应该用哪个?

    [Flutter翻译]Flutter中延迟的代码执行

    在这篇文章中,我想带你进入Flutter的深处,了解更多关于调度代码执行的小旅程。作为一个对话的开始,让我们假设我们正在使用标准的BLoC架构,使用Provider库构建一个应用程序。为了使这个任务具有挑战性,在打开一个新的屏幕后,我们将不得不发起一个网络请求,通过互联网获取一些东西。在这种情况下,我们有几个选择来发起我们的请求。

    1.在显示我们的屏幕之前获取数据,然后在预先加载数据的情况下显示它,这可能不是最好的选择。这可能不是最好的选择。如果您决定只获取所需的部分数据,您很可能会加载大量不必要的数据或用旋转器阻塞用户界面。

    1. BLoC中启动加载过程,就在屏幕显示之前,当创建BLoC本身或使用协调器对象为您启动它。如果您想保持架构的整洁,这将是推荐的方法。

    2. 在屏幕的initState中启动加载过程,尝试将这个逻辑封装在屏幕本身。

    第三种方案在架构正确性方面可能不是最好的,但实际上是Flutter世界中相当常见的方法。让我们来研究一下,因为它在现实世界的场景中完美地演示了我们的主题。

    为了演示的目的,这里是一个示例代码。注意到它有什么问题吗?

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    void main() {
      runApp(
        MaterialApp(
          title: 'Demo',
          home: ChangeNotifierProvider(
            create: (_) => MyHomePageBloc(),
            child: MyHomePage(),
          ),
        ),
      );
    }
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key}) : super(key: key);
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    class _MyHomePageState extends State<MyHomePage> {
      @override
      void initState() {
        super.initState();
        context.read<MyHomePageBloc>().fetchData();
      }
      @override
      Widget build(BuildContext context) {
        final bloc = context.watch<MyHomePageBloc>();
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: bloc.loading ? CircularProgressIndicator() : Text(bloc.data),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () => context.read<MyHomePageBloc>().fetchData(),
            tooltip: 'Fetch',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    class MyHomePageBloc with ChangeNotifier {
      String data = "Loading";
      bool loading = false;
      void fetchData() {
        loading = true;
        data = "Loading";
        notifyListeners();
        Future.delayed(Duration(seconds: 3), () {
          loading = false;
          data = "Done";
          notifyListeners();
        });
      }
    }
    

    乍一看,似乎一切正常。然而,如果你运行它,它将不可避免地崩溃,你会在日志中看到类似的东西。‘package:flutter/src/widgets/framework.dart’: Failed assertion: line 4349 pos 12: ‘!_dirty’: is not true.

    这个错误表明,我们正试图在构建时修改widget树。小组件的 initState 方法是在构建过程的中间调用的,所以从那里修改小组件树的任何尝试都会失败。在我们的例子中,当调用fetch方法时,它会同步执行notifyListeners(),从而导致widget树发生变化。

    当你试图做更多看似不相关的事情时,可能会遇到类似的错误。例如,显示一个对话,也会因为类似的原因而失败,因为上下文( Element)当前还没有挂载在小组件树中。

    无论你想做什么,你必须延迟代码执行,直到构建过程完成。换句话说,你需要异步执行你的代码。现在说说我们的选择。

    如何在Flutter中延迟代码执行?

    通过在互联网上研究这个话题,我整理了一个最常见的推荐解决方案列表。你甚至可以找到一些额外的选项,但这里是最引人注目的选项。

    • scheduleMicrotask
    • Future<T>.microtask
    • Future<T>
    • Future<T>.delayed
    • Timer.run
    • WidgetsBinding.addPostFrameCallback
    • SchedulerBinding.addPostFrameCallback

    你可能会说,这选项还真不少,你说的没错。说到我们上述的问题,任何一种都能解决。不过,既然我们面对如此多的选择,那就让我们放纵一下自己的好奇心,试着了解一下它们之间的区别。

    事件循环和多线程

    你可能知道,Dart是一个单线程系统。令人惊讶的是,你的应用程序可以同时做多件事情,或者至少看起来是这样。这就是事件循环的作用。事件循环从字面上看就是一个执行预定事件的无尽循环(对于iOS开发者来说是Run Loop)。这些事件(或者只是代码块,如果你喜欢的话)必须是轻量级的,否则,你的应用会感觉滞后或完全冻结。每一个事件,如按钮按下或网络响应,都被安排在事件队列中,并等待被事件循环接收和执行。这种设计模式在UI和其他处理任何类型事件的系统中相当常见。这个概念可能很难用两句话解释清楚,所以如果你是新手的话,我建议你在旁边看点东西。不要想得太多,我们从字面上看,我们说的是一个简单的无限循环和一个计划执行的任务列表(代码块),每次循环的迭代都是一个。

    我们即将学习的Dart事件循环聚会的特殊嘉宾是Microtask 。我们的Event Loop里面有额外的队列,这就是Microtask队列。关于这个队列,唯一需要注意的是,在事件本身执行之前,所有排定在其中的任务都会在事件循环的一次迭代中被执行。

    [Flutter翻译]Flutter中延迟的代码执行

    遗憾的是,这方面的资料并不多,我看到的最好的解释可以在这里或这里的网络档案中找到。

    有了这些知识之后,让我们看看上面列出的所有选项,了解它们的工作方式和它们之间的区别。

    事件

    任何进入事件队列的东西。这是你在Flutter中调度异步任务的默认方法。调度一个事件,我们把它添加到事件队列中,由事件循环来接收。这种方法被许多Flutter机制使用,如I/O、手势事件、定时器等。

    定时器

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

    如何使用。

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

    Future<T>Future<T>.delayed

    一个众所周知且被广泛使用的Dart功能。这可能会让人感到惊讶,但如果你看一下引擎盖下的东西,你会发现只不过是前述定时器的一个包装。

    如何使用。

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

    内部实现(链接)。

    factory Future(FutureOr<T> computation()) {
      _Future<T> result = new _Future<T>();
      Timer.run(() {
        try {
          result._complete(computation());
        } catch (e, s) {
          _completeWithErrorCallback(result, e, s);
        }
      });
      return result;
    }
    

    微型任务

    如前所述,所有预定的微任务都会在下一个预定事件之前执行。建议避免使用这个队列,除非绝对需要异步执行代码,但在事件队列的下一个事件之前。你也可以把这个队列看作是属于上一个事件的任务队列,因为它们将在下一个事件之前完成。过载这个队列可能会完全冻结你的应用程序,因为它必须先执行这个队列中的所有内容,然后才能进入其事件队列的下一个迭代,例如处理用户输入甚至渲染应用程序本身。尽管如此,这里有我们的选择。

    scheduleMicrotask

    顾名思义,在微任务队列中调度一个块状代码。类似于定时器,如果出了问题,会让应用程序崩溃。

    如何使用。

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

    Future<T>.microtask

    类似于我们之前看到的,将我们的微任务包装在一个try-catch块中,以一种漂亮简洁的方式返回执行结果或错误。

    如何使用。

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

    内部实现(链接)。

    factory Future.microtask(FutureOr<T> computation()) {
      _Future<T> result = new _Future<T>();
      scheduleMicrotask(() {
        try {
          result._complete(computation());
        } catch (e, s) {
          _completeWithErrorCallback(result, e, s);
        }
      });
      return result;
    }
    

    后帧回调

    之前的两种方法涉及到低级别的事件循环机制,而我们现在则转向Flutter领域。这个回调在渲染管道完成时被调用,所以它与widget的生命周期相关联。当它被调度时,它只被调用一次,而不是在每个帧上。使用addPostFrameCallback方法,您可以安排一个或多个回调,以便在帧构建完成后执行。所有预定的回调都将在帧的末尾按照添加的顺序被执行。当这个回调被调用的时候,保证了widget构建过程的完成。通过一些烟雾和镜子,你甚至可以访问widget的布局( RenderBox),比如它的大小,以及做其他各种不推荐的黑客。回调本身将在正常的事件队列中运行,Flutter默认使用的几乎是所有的事件。

    调度器绑定

    这是一个负责绘图回调的 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");
      });
    

    结束语

    现在我们了解了这么多细节,你可以对如何安排你的代码做出一个深思熟虑的决定。作为一个经验法则,如果你需要你的上下文或与Layout或UI相关的东西,使用addPostFrameCallback。在任何其他情况下,用Future<T>Future<T>.delayed在事件队列中调度应该是足够的。微任务队列是非常小众的东西,你可能永远不会遇到,但还是值得了解。当然,如果你的任务很重,你就要考虑创建一个Isolate,你可能已经猜到了,这个Isolate将由事件队列来传达。但这是另一篇文章的主题。谢谢你的时间,我们下次再见。

    原文发表于2021年2月7日,https://oleksandrkirichenko.com。


    通过www.DeepL.com/Translator(免费版)翻译


    起源地下载网 » [Flutter翻译]Flutter中延迟的代码执行

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元