美文网首页
flutter 同级Future执行顺序探究

flutter 同级Future执行顺序探究

作者: 简单coder | 来源:发表于2022-02-14 18:23 被阅读0次

    上周六一次视频面中,我在回答问题中提到一句,flutter 的同级Future 都是顺序执行的,面试官立刻问我,你确定吗,一下子把我问住了,这个结论是我忘记在哪里看到的 blog 上面提到的,当时就一直记在了心里,我没有去验证过,也没有需要使用的场景,所以只是当一个结论记着,自己也说不出个所以然来,所以被人问住也是很正常的.很羞愧的是,上周之前,我并不知道 flutter 面试需要考些什么,甚至自以为考官会问我应用层的问题,真是面的一塌糊涂.
    话说回来,本着技术探讨的初心,我决定自己测试并探究下这,同级Future 到底是有序还是无序的.

    代码测试

    import 'dart:async';
    
    import 'package:flutter/material.dart';
    import 'package:flutter_psd/common/utils/psdllog.dart';
    
    class AysncTestPage extends StatelessWidget {
      const AysncTestPage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(
              '异步测试',
              style: TextStyle(),
            ),
          ),
          body: GestureDetector(onTap: () {
            // testMultipleAsync();
        testAsync();
          }),
        );
      }
    
      void testAsync() {
        // 第一种
        Future(() => psdllog(1),);
        Future(() => psdllog(2));
        Future(() => psdllog(3));
        Future(() => psdllog(4));
        Future(() => psdllog(5));
        Future(() => psdllog(6));
        Future(() => psdllog(7));
        Future(() => psdllog(8));
        Future(() => psdllog(9));
        Future(() => psdllog(10));
        Future(() => psdllog(11));
        Future(() => psdllog(12));
    
        // 第二种
        // Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(11));
        // Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(22));
        // Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(33));
        // Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(44));
        // Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(55));
        // Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(66));
        // Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(77));
        // Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(88));
        // Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(99));
    
        // 第三种
        // for (var i = 0; i < 10000; i++) {
        //   Future.delayed(Duration(milliseconds: 1500)).then((value) => psdllog(i));
        // }
    
        // 第四种
        // Future(() => psdllog(111111),);
        // Future.delayed(Duration(milliseconds: 500), () {
        //   psdllog(2222222);
        // });
        // Future(() => psdllog(333333),);
      }
    
      testMultipleAsync() {
        // 测试各类 async
        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");
      }
    }
    
    

    代码中,我测试的逻辑很简单,在点击手势中调用testAsync方法,然后多次测试我展示的前三种方法,结果均是先加入的先执行,后加入的后执行,这与我们的iOS 中的串行队列类似.

    源码查找

    Future实现

    123
    可以看出来,future 的调用是在 Timer 中执行的,Timer.run实际上是创建了一个 duration 为 zero 的 timer 去run
    static void run(void Function() callback) {
        new Timer(Duration.zero, callback);
      }
    

    现在问题就转换成了,多个Timer的 duration为 zero 的时候,调用顺序是什么

    Timer 源码

    factory Timer(Duration duration, void Function() callback) {
        if (Zone.current == Zone.root) {
          // No need to bind the callback. We know that the root's timer will
          // be invoked in the root zone.
          return Zone.current.createTimer(duration, callback);
        }
        return Zone.current
            .createTimer(duration, Zone.current.bindCallbackGuarded(callback));
      }
    

    timer 的创建是在调用 zone 的createTimer生成的.我们最终可以追溯到的是

    Timer createTimer(Duration duration, void f()) {
        var implementation = this._createTimer;
        ZoneDelegate parentDelegate = implementation.zone._parentDelegate;
        CreateTimerHandler handler = implementation.function;
        return handler(implementation.zone, parentDelegate, this, duration, f);
      }
    

    其实到这里,还是无法看出实际 timer 的执行步骤





    整个dart:async 中 timer 的代码在上面展示了,不知道是什么原因,真正的_timer 实现我并没有找到,Zone 函数中关于 timer 的创建都被抛给 delegate(rootZone)了,到目前为止,我们只能知道_createTimer的执行是按顺序执行下来的,代码到这里为止,同级的Future 是按顺序_createTimer 的,但是 实际上 timer 的创建,即Timer._createTimer,我并没有找到,线索到这就断掉了...

    timer 的真正实现

    终于,在多番查找后,我终于在网上找到了 dart 对于 timer 的源码实现, 戳这
    这篇文章讲的很细很细,作者大逗大人对于每个代码都增加了注释,建议每个人认真反复阅读三遍,需要了解人可以全部看看.
    回到正题,我们本着我们探究问题去查找,我只截取对我们问题有帮助的部分代码.

    • 创建 timer 的实现
      static Timer _createTimer(
          void callback(Timer timer), int milliSeconds, bool repeating) {
        //milliSeconds不能小于0,小于0也就意味着超时,需要立即执行。
        if (milliSeconds < 0) {
          milliSeconds = 0;
        }
    
        //获取当前时间
        int now = VMLibraryHooks.timerMillisecondClock();
        //得到Timer的唤醒时间
        int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds);
        
        //创建一个Timer对象
        _Timer timer =
            new _Timer._internal(callback, wakeupTime, milliSeconds, repeating);
        //将新创建的Timer放到适当的结构中,并在必要时进行对应的通知。
        //如果Timer中是异步任务,则加入到链表中,否则加入到二叉堆中
        timer._enqueue();
        return timer;
      }
    

    注意这里, 将新创建的Timer放到适当的结构中,并在必要时进行对应的通知。如果Timer中是非异步任务(即 zero_event, 时间戳为0的任务),则加入到链表中,否则加入到二叉堆中,但是不管他们是链表形式插入还是二叉堆形式插入,他们的存取都是有顺序的.

    • timer的排序
    //将Timer添加到二叉堆或者链表中,如果唤醒时间相同则按照先进先出的规则来取出
      void _enqueue() {
        if (_milliSeconds == 0) {
          if (_firstZeroTimer == null) {
            _lastZeroTimer = this;
            _firstZeroTimer = this;
          } else {
            _lastZeroTimer._indexOrNext = this;
            _lastZeroTimer = this;
          }
          // Every zero timer gets its own event.
          _notifyZeroHandler();
        } else {
          _heap.add(this);
          if (_heap.isFirst(this)) {
            _notifyEventHandler();
          }
        }
      }
    

    可以推断出,在我执行之前的代码中

        Future(() => psdllog(1),);
        Future(() => psdllog(2));
        Future(() => psdllog(3));
        Future(() => psdllog(4));
        Future(() => psdllog(5));
        Future(() => psdllog(6));
    

    1到9的 Future 代码会按照从上而下的顺序被添加到链表中

        Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(11));
        Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(22));
        Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(33));
        Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(44));
        Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(55));
        Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(66));
        Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(77));
        Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(88));
        Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(99));
    

    11-99会被添加到二叉堆中,我们不用管二叉堆内部是如何实现的,只需要知道这个

    //首先根据唤醒时间来排序,如果唤醒时间相同则根据timer的_id来排序
      int _compareTo(_Timer other) {
        int c = _wakeupTime - other._wakeupTime;
        if (c != 0) return c;
        return _id - other._id;
      }
    

    对于时间相同的 timer,判断 timer 的 _id,那_timer 的 id 是如何被赋值的呢? 在上面的_createTimer其实已经提到过了

    //创建一个Timer对象
      _Timer._internal(
          this._callback, this._wakeupTime, this._milliSeconds, this._repeating)
          : _id = _nextId();
    
    //获取下一个可用id
      static int _nextId() {
        var result = _idCount;
        _idCount = (_idCount + 1) & _ID_MASK;
        return result;
      }
    

    看到这里其实就能发现,对于Delay 时间相同的异步任务,timer 的 id 是按自动递增1的,即先执行 Timer.run 的 timer id 更小点,这也能推断出延时任务的 Future,duration 相同的话,先运行的Future 先执行.

    • 调用 timer
    static void _handleMessage(msg) {
        var pendingTimers;
        if (msg == _ZERO_EVENT) {
          //获取包含异步任务的timer
          pendingTimers = _queueFromZeroEvent();
        } else {
          _scheduledWakeupTime = null; // Consumed the last scheduled wakeup now.
          //获取已经超时的timer
          pendingTimers = _queueFromTimeoutEvent();
        }
        //执行Timer的回调方法
        _runTimers(pendingTimers);
        //如果当前没有待执行的Timer,则通知event handler或者关闭port
        _notifyEventHandler();
      }
    

    跳转到_queueFromZeroEvent()

    static List _queueFromZeroEvent() {
        var pendingTimers = new List();
        
        //从二叉堆中查询到期时间小于_firstZeroTimer的timer,并加入到一个List中
        var timer;
        while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) {
          timer = _heap.removeFirst();
          pendingTimers.add(timer);
        }
        //获取链表中的第一个timer
        timer = _firstZeroTimer;
        _firstZeroTimer = timer._indexOrNext;
        timer._indexOrNext = null;
        pendingTimers.add(timer);
        return pendingTimers;
      }
    

    这里可以看出,调用时取的是_firstZeroTimer,在该方法中,会将二叉堆中唤醒时间比链表中的第一个timer对象唤醒时间还短的timer对象加入到集合pendingTimers中,然后再将链表中的第一个timer对象加入到集合pendingTimers中。

    取非延时的 timer 是从 链表的第一个一个个往后取的.

    取异步任务的 timer是从二叉树 先按时间升序,然后再按_id 升序

    所以,Duration 相同的前提下,Future 的同级代码一定会按照顺序执行timer 中的function

    拖展


    答案是1,-500,2,3,500,其实-500的 future 其实也是被添加到异步任务中,只不过,在执行过程中,
    _notifyEventHandler-> _handleMessage-> _queueFromTimeoutEvent(获取超时的 timer),执行.

    iOS 的思考

    future 同级按顺序执行,与 iOS 串行队列很像,都是先进先出,按顺序执行,只不过 iOS 的不开源,导致我做了这么久,也没去思考过,iOS 串行队列的实现到底是怎么实现的.dart 开源的好处也就是体现在了这里,

    一个小问题

    在研究这个的同时,我也顺便看了下其他的关于异步任务,微队列的相关知识,也算是真正的了解了 flutter 的 future,scheduleMicrotask等执行顺序,那么,现在压力来到了读者这边,下面这道题是我在网上看到的一道比较全面的题目了

    test() {
        // 测试各类 async
        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");
      }
    

    还有下面这道是我想出来的题目,跟上面类似,不过增加 Future.microtask,俗话说,两道题全对,那才叫真的理解了,希望读者们可以讲对,并且讲明为什么.

    test() {
        psdllog(0);
    
        var future1 = Future(() => psdllog(1));
    
        var future2 = Future.value(0).then((value) => psdllog(2));
    
        Future.value(0).then((value) => psdllog(3));
    
        Future.microtask(() => psdllog(4));
    
        scheduleMicrotask(() => psdllog(5));
    
        future1.then((value) {
    
          psdllog(6);
    
          scheduleMicrotask(() {
    
            psdllog(7);
    
            Future(() => psdllog(8),);
          });
        });
        future2.then((value) => psdllog(9));
    
        Future(() => psdllog(10),);
    
        psdllog(11);
      }
    

    这些题目才算是真正的面试八股文,汇聚了 部分 future会被放到微队列,微队列与事件队列的执行循环,future 的 then 函数调用顺序,微队列任务或事件任务中调用 future 等执行顺序,你们能真正弄懂这个考题吗,后面我会找时间把这部分内容补上的,唉,坑越埋越多了~
    最近一段时间,先把面试八股文背好先,找个稳妥点的工作,如果要更新文章的话,估计都会跟面试有搭噶,其他的坑后面再说~

    相关文章

      网友评论

          本文标题:flutter 同级Future执行顺序探究

          本文链接:https://www.haomeiwen.com/subject/gymclrtx.html