flutter 同級Future執(zhí)行順序探究

上周六一次視頻面中,我在回答問題中提到一句,flutter 的同級Future 都是順序執(zhí)行的,面試官立刻問我,你確定嗎,一下子把我問住了,這個結論是我忘記在哪里看到的 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方法,然后多次測試我展示的前三種方法,結果均是先加入的先執(zhí)行,后加入的后執(zhí)行,這與我們的iOS 中的串行隊列類似.

源碼查找

Future實現

123

可以看出來,future 的調用是在 Timer 中執(zhí)行的,Timer.run實際上是創(chuàng)建了一個 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 的創(chuàng)建是在調用 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 的執(zhí)行步驟





整個dart:async 中 timer 的代碼在上面展示了,不知道是什么原因,真正的_timer 實現我并沒有找到,Zone 函數中關于 timer 的創(chuàng)建都被拋給 delegate(rootZone)了,到目前為止,我們只能知道_createTimer的執(zhí)行是按順序執(zhí)行下來的,代碼到這里為止,同級的Future 是按順序_createTimer 的,但是 實際上 timer 的創(chuàng)建,即Timer._createTimer,我并沒有找到,線索到這就斷掉了...

timer 的真正實現

終于,在多番查找后,我終于在網上找到了 dart 對于 timer 的源碼實現, 戳這
這篇文章講的很細很細,作者大逗大人對于每個代碼都增加了注釋,建議每個人認真反復閱讀三遍,需要了解人可以全部看看.
回到正題,我們本著我們探究問題去查找,我只截取對我們問題有幫助的部分代碼.

  • 創(chuàng)建 timer 的實現
  static Timer _createTimer(
      void callback(Timer timer), int milliSeconds, bool repeating) {
    //milliSeconds不能小于0,小于0也就意味著超時,需要立即執(zhí)行。
    if (milliSeconds < 0) {
      milliSeconds = 0;
    }

    //獲取當前時間
    int now = VMLibraryHooks.timerMillisecondClock();
    //得到Timer的喚醒時間
    int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds);
    
    //創(chuàng)建一個Timer對象
    _Timer timer =
        new _Timer._internal(callback, wakeupTime, milliSeconds, repeating);
    //將新創(chuàng)建的Timer放到適當的結構中,并在必要時進行對應的通知。
    //如果Timer中是異步任務,則加入到鏈表中,否則加入到二叉堆中
    timer._enqueue();
    return timer;
  }

注意這里, 將新創(chuàng)建的Timer放到適當的結構中,并在必要時進行對應的通知。如果Timer中是非異步任務(即 zero_event, 時間戳為0的任務),則加入到鏈表中,否則加入到二叉堆中,但是不管他們是鏈表形式插入還是二叉堆形式插入,他們的存取都是有順序的.

  • timer的排序
//將Timer添加到二叉堆或者鏈表中,如果喚醒時間相同則按照先進先出的規(guī)則來取出
  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();
      }
    }
  }

可以推斷出,在我執(zhí)行之前的代碼中

    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其實已經提到過了

//創(chuàng)建一個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;
  }

看到這里其實就能發(fā)現,對于Delay 時間相同的異步任務,timer 的 id 是按自動遞增1的,即先執(zhí)行 Timer.run 的 timer id 更小點,這也能推斷出延時任務的 Future,duration 相同的話,先運行的Future 先執(zhí)行.

  • 調用 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();
    }
    //執(zhí)行Timer的回調方法
    _runTimers(pendingTimers);
    //如果當前沒有待執(zhí)行的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 的同級代碼一定會按照順序執(zhí)行timer 中的function

拖展


答案是1,-500,2,3,500,其實-500的 future 其實也是被添加到異步任務中,只不過,在執(zhí)行過程中,
_notifyEventHandler-> _handleMessage-> _queueFromTimeoutEvent(獲取超時的 timer),執(zhí)行.

iOS 的思考

future 同級按順序執(zhí)行,與 iOS 串行隊列很像,都是先進先出,按順序執(zhí)行,只不過 iOS 的不開源,導致我做了這么久,也沒去思考過,iOS 串行隊列的實現到底是怎么實現的.dart 開源的好處也就是體現在了這里,

一個小問題

在研究這個的同時,我也順便看了下其他的關于異步任務,微隊列的相關知識,也算是真正的了解了 flutter 的 future,scheduleMicrotask等執(zhí)行順序,那么,現在壓力來到了讀者這邊,下面這道題是我在網上看到的一道比較全面的題目了

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會被放到微隊列,微隊列與事件隊列的執(zhí)行循環(huán),future 的 then 函數調用順序,微隊列任務或事件任務中調用 future 等執(zhí)行順序,你們能真正弄懂這個考題嗎,后面我會找時間把這部分內容補上的,唉,坑越埋越多了~
最近一段時間,先把面試八股文背好先,找個穩(wěn)妥點的工作,如果要更新文章的話,估計都會跟面試有搭噶,其他的坑后面再說~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容