天天看點

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

      小菜前段時間簡單研究了一下 Dart 單線程實作異步的操作,今天繼續學習 Dart 的事件機制的任務排程;

任務排程

      Dart 是單線程的,一個 Flutter 程式由一個或多個 isolate 組成,預設的執行方法均是在 main isolate 中;一個 isolate 中包含一個 Event Loop 和一個 Task Queue,而 Task Queue 包含 MicroTask Queue 微事件隊列和 Event Queue 事件隊列兩種;

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

      Dart 的事件機制是根據任務排程優先級來實作的;其中将任務添加到 MicroTask Queue 微事件隊列的方式有 scheduleMicrotask() 和 Future.microtask() 兩種;而将任務添加到 Event Queue 事件隊列一般通過 Future 的相關構造方法實作;

      MicroTask Queue 微事件隊列的執行優先級高于 Event Queue 事件隊列,而小菜簡單了解,兩種均類似于 Android 線程中的異步操作,隻是 MicroTask Queue 微事件隊列優先級相對更高;

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

Dart 的事件執行順序如圖所示;

  1. 啟動 app 後優先執行 main() 方法中的同步方法;
  2. 檢視 MicroTask Queue 是否為空,若不為空,優先循環執行 MicroTask Queue 中的 MicroTask,直到隊列為空;
  3. 若 MicroTask Queue 隊列為空則檢視 Event Queue 事件隊列,若不為空,則循環執行 Event Queue 中的 Event 事件,直到隊列為空;
  4. 等兩個隊列的任務均執行完成後結束;

      Tips: 當任務隊列執行 MicroTask Queue 微事件隊列時,Event Queue 事件隊列被卡住,即應用無法繪制圖形,處理滑鼠點選事件,無法對 I/O 事件做出反應等;

案例嘗試

      每個 isolate 有各自的記憶體塊和 Event Loop 是互相隔離的,小菜隻嘗試單個 isolate 中的微事件隊列和事件隊列的執行順序;

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#
  1. 小菜優先嘗試最基本的 Future 構造函數和 MicroTask 的執行順序;
_taskQueue01() {
  Future(() => print('TaskQueue -> Future A'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask B'));
  Future(() => print('TaskQueue -> Future C'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
}
           

      根據上述執行順序,首先執行 main() 中同步的日志輸出 Click start -> end,之後優先執行 MicroTask 微事件 scheduleMicrotask A -> C;再之後執行添加到 EventTask 事件隊列的 Future() 構造函數 Future A -> C;最後執行因延遲 2s 加入事件隊列的 Future B;

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

2. 小菜在【案例一】的基礎上嘗試添加 Future.microtask() 構造函數;

_taskQueue02() {
  Future(() => print('TaskQueue -> Future A'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'));
  Future.microtask(() => print('TaskQueue -> Future.microtask B'));
  Future(() => print('TaskQueue -> Future C'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
}
           

      簡單了解 Future.microtask() 函數源碼,其實就是封裝了一個 scheduleMicrotask() 微事件,是以微事件隊列執行順序為 scheduleMicrotask A -> B -> C;

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

3. 小菜在【案例二】的基礎上嘗試添加 Future.sync() 構造函數;

_taskQueue03() {
  Future(() => print('TaskQueue -> Future A'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.sync(() => print('TaskQueue -> Future.sync D'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'));
  Future.microtask(() => print('TaskQueue -> Future.microtask B'));
  Future(() => print('TaskQueue -> Future C'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
}
           

      Future.sync() 為同步方法,會立即執行,是以是在 main() 點選同步日志 start 和 end 之間的;

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

4. 小菜嘗試添加 Future.then() 級聯方法;

_taskQueue04() {
  Future(() => print('TaskQueue -> Future A'))
      .then((_) => print('TaskQueue -> Future A -> then()01'))
      .then((_) => print('TaskQueue -> Future A -> then()02'));
  Future.sync(() => print('TaskQueue -> Future.sync D'))
      .then((_) => print('TaskQueue -> Future.sync D -> then()01'))
      .then((_) => print('TaskQueue -> Future.sync D -> then()02'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()01'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()02'));
  Future.microtask(() => print('TaskQueue -> Future.microtask B'))
      .then((_) => print('TaskQueue -> Future.microtask B -> then()01'))
      .then((_) => print('TaskQueue -> Future.microtask B -> then()02'));
  Future(() => print('TaskQueue -> Future C'))
      .then((_) => print('TaskQueue -> Future C -> then()01'))
      .then((_) => print('TaskQueue -> Future C -> then()02'));
}
           

      Future.then() 級聯函數是按照順序執行的,執行完第一個 then() 函數後才會執行第二個 then();且小菜了解為 then() 函數會在 Future() 執行之後立即執行,可以看作是存放在微事件隊列中;是以整體的執行順序是 sync()-> sync().then() -> microtask() -> microtask().then() -> Future() -> Future().then() -> delayed() -> delayed().then();

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

5. 小菜在【案例三 + 四】的基礎上添加 Future.whenComplete() 方法;

_taskQueue05() {
  Future(() => print('TaskQueue -> Future A'))
      .then((_) => print('TaskQueue -> Future A -> then()01'))
      .then((_) => print('TaskQueue -> Future A -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future A -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.sync(() => print('TaskQueue -> Future.sync D'))
      .then((_) => print('TaskQueue -> Future.sync D -> then()01'))
      .then((_) => print('TaskQueue -> Future.sync D -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.sync D -> whenComplete()'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()01'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.delayed B -> whenComplete()'));
  Future.microtask(() => print('TaskQueue -> Future.microtask B'))
      .then((_) => print('TaskQueue -> Future.microtask B -> then()01'))
      .then((_) => print('TaskQueue -> Future.microtask B -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.microtask B -> whenComplete()'));
  Future(() => print('TaskQueue -> Future C'))
      .then((_) => print('TaskQueue -> Future C -> then()01'))
      .then((_) => print('TaskQueue -> Future C -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future C -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
}
           

      Future.whenComplete() 是在 Future() 及 then() 函數執行完成之後立即執行;小菜也簡單看作是存放在微事件隊列中;依照小菜的測試案例來看,sync 執行同步任務完成後,會循環周遊微事件隊列,其中 scheduleMicrotask A 是在 sync() 函數之前加入 MicroTask Queue 中,是以是先執行 scheduleMicrotask A 再執行 sync() 之後的 then() 和 whenComplete() 方法;

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

6. 小菜嘗試在 then() 函數中執行新的事件隊列;

_taskQueue06() {
  Future(() => print('TaskQueue -> Future A'))
      .then((_) {
        print('TaskQueue -> Future A -> then()01');
        return Future.delayed(Duration(seconds: 1), () => print('TaskQueue -> Future.delayed D'));
      })
      .then((_) => print('TaskQueue -> Future A -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future A -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()01'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.delayed B -> whenComplete()'));
  Future(() => print('TaskQueue -> Future C'))
      .then((_) {
        print('TaskQueue -> Future C -> then()01');
        return scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
      })
      .then((_) => print('TaskQueue -> Future C -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future C -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask B'));
}
           

    a. 首先執行 MicroTask Queue 微事件隊列中的 scheduleMicrotask A -> B;

    b. 之後以 FIFO 先進先出的順序執行 EventTask Queue 事件隊列中 Task;先執行 Future A,之後是 A 中 then()01,此時小菜設定了一個 1s 的延遲 Future.delayed D,因為 then() 的級聯函數是需要等前面的 then() 函數執行完成後才能執行;是以優先執行 EventTask Queue 中的 Future C;

    c. 執行完 Future C 之後執行 then()01,此時小菜設定了一個 MicroTask,依照小菜的了解,then() / whenComplete() 均存放在 MicroTask Queue 中,是以新設定的 MicroTask 會放置在 MicroTask Queue 末尾,等 then()02 和 whenComplete() 再執行

    d. 此時 EventTask Queue 事件隊列中已執行完畢,在 1s 後添加了新的 Future.delayed D 并執行;

    e. 待 Future.delayed D 執行結束或,Future A 的第二個級聯 then() 函數和 whenComplete() 開始執行;

    f. 最後執行延遲最久的 2s 的 Future.delayed B 及其 then() / whenComplete() 函數;

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

7. 小菜在測試過程中,與【案例六】代碼幾乎一緻,隻是在 Future.then() 中調用 Future() 時少了 return;

_taskQueue07() {
  Future(() => print('TaskQueue -> Future A'))
      .then((_) {
        print('TaskQueue -> Future A -> then()01');
        Future.delayed(Duration(seconds: 1), () => print('TaskQueue -> Future.delayed D'));
      })
      .then((_) => print('TaskQueue -> Future A -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future A -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask A'));
  Future.delayed(Duration(seconds: 2), () => print('TaskQueue -> Future.delayed B'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()01'))
      .then((_) => print('TaskQueue -> Future.delayed B -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future.delayed B -> whenComplete()'));
  Future(() => print('TaskQueue -> Future C'))
      .then((_) {
        print('TaskQueue -> Future C -> then()01');
        scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask C'));
      })
      .then((_) => print('TaskQueue -> Future C -> then()02'))
      .whenComplete(() => print('TaskQueue -> Future C -> whenComplete()'));
  scheduleMicrotask(() => print('TaskQueue -> scheduleMicrotask B'));
}
           

      小菜測試發現,雖然隻是少了兩個 return 但是執行順序卻變化很大;

    a. 首先執行微事件隊列中的 scheduleMicrotask A -> B

    b. 之後執行 EventTask Queue 中的 Future A,執行第一個 print(then()01) 之後,小菜設定了 Future.delayed D,因為無需傳回,是以将 Future.delayed D 事件隊列末位後第一個 then() 函數已完成執行,此時可以執行第二個 then()02 和 whenComplete() 函數;

    c. 繼續執行 EventTask Queue 中的 Future C,執行第一個 print(then()01) 之後,小菜設定了 scheduleMicrotask() C,無需 return,将 scheduleMicrotask() C 添加到微事件隊列末位,此時第一個 then() 函數以完成執行,執行微事件隊列中的第二個 then()02 和 whenComplete() 函數,再之後執行微事件隊列中最後的 scheduleMicrotask() C;

    d. 之後執行事件隊列末位的 1s 的 Future.delayed D,此時事件隊列已為空;

    e. 最後執行 2s 添加到事件隊列中的 Future.delayed B 及其 then() / whenComplete() 函數;

【Flutter 專題】95 圖解 Dart 單線程實作異步處理之 Task Queue #yyds幹貨盤點#

彙總小結

  1. Dart 的任務排程模型是單線程輪詢,不是基于時間排程的;我們可以知道各個事件的排程順序,但無法明确得知各個事件排程的時間;例如:延遲 2s 并非一定是 2s 後執行,需要等之前的任務排程結束後才會執行;
  2. 注意級聯函數中的新的任務排程是否需要傳回結果後再繼續;
  3. 實際中盡量在 EventTask 中執行耗時操作,減少在 MicroTask 中執行;若任務耗時時間較長可以嘗試用 isolate 開啟新的異步線程執行;

繼續閱讀