【Flutter 專題】95 圖解 Dart 單執行緒實現非同步處理之 Task Queue

阿策小和尚發表於2021-07-14

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

任務排程

      Dart 是單執行緒的,一個 Flutter 程式由一個或多個 isolate 組成,預設的執行方法均是在 main isolate 中;一個 isolate 中包含一個 Event Loop 和一個 Task Queue,而 Task Queue 包含 MicroTask Queue 微事件佇列和 Event Queue 事件佇列兩種;       Dart 的事件機制是根據任務排程優先順序來實現的;其中將任務新增到 MicroTask Queue 微事件佇列的方式有 scheduleMicrotask()Future.microtask() 兩種;而將任務新增到 Event Queue 事件佇列一般通過 Future 的相關構造方法實現;

      MicroTask Queue 微事件佇列的執行優先順序高於 Event Queue 事件佇列,而小菜簡單理解,兩種均類似於 Android 執行緒中的非同步操作,只是 MicroTask Queue 微事件佇列優先順序相對更高; 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 中的微事件佇列和事件佇列的執行順序;

  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 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 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() 點選同步日誌 startend 之間的; 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() 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() 方法; 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,之後是 Athen()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()02whenComplete() 再執行     d. 此時 EventTask Queue 事件佇列中已執行完畢,在 1s 後新增了新的 Future.delayed D 並執行;     e. 待 Future.delayed D 執行結束或,Future A 的第二個級聯 then() 函式和 whenComplete() 開始執行;     f. 最後執行延遲最久的 2sFuture.delayed B 及其 then() / whenComplete() 函式; 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()02whenComplete() 函式;     c. 繼續執行 EventTask Queue 中的 Future C,執行第一個 print(then()01) 之後,小菜設定了 scheduleMicrotask() C,無需 return,將 scheduleMicrotask() C 新增到微事件佇列末位,此時第一個 then() 函式以完成執行,執行微事件佇列中的第二個 then()02whenComplete() 函式,再之後執行微事件佇列中最後的 scheduleMicrotask() C;     d. 之後執行事件佇列末位的 1sFuture.delayed D,此時事件佇列已為空;     e. 最後執行 2s 新增到事件佇列中的 Future.delayed B 及其 then() / whenComplete() 函式;

彙總小結

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

      Task Queue 案例嘗試


      小菜對任務排程的理解還不夠深入,如有錯誤請多多指導!

來源: 阿策小和尚

相關文章