小菜前段時間簡單研究了一下 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 的事件執行順序如圖所示;
- 啟動 app 後優先執行 main() 方法中的同步方法;
- 檢視 MicroTask Queue 是否為空,若不為空,優先迴圈執行 MicroTask Queue 中的 MicroTask,直到佇列為空;
- 若 MicroTask Queue 佇列為空則檢視 Event Queue 事件佇列,若不為空,則迴圈執行 Event Queue 中的 Event 事件,直到佇列為空;
- 等兩個佇列的任務均執行完成後結束;
Tips: 當任務佇列執行 MicroTask Queue 微事件佇列時,Event Queue 事件佇列被卡住,即應用無法繪製圖形,處理滑鼠點選事件,無法對 I/O 事件做出反應等;
案例嘗試
每個 isolate 有各自的記憶體塊和 Event Loop 是互相隔離的,小菜只嘗試單個 isolate 中的微事件佇列和事件佇列的執行順序;
- 小菜優先嚐試最基本的 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() 點選同步日誌 start 和 end 之間的; 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,之後是 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() 函式; 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() 函式;
彙總小結
- Dart 的任務排程模型是單執行緒輪詢,不是基於時間排程的;我們可以知道各個事件的排程順序,但無法明確得知各個事件排程的時間;例如:延遲 2s 並非一定是 2s 後執行,需要等之前的任務排程結束後才會執行;
- 注意級聯函式中的新的任務排程是否需要返回結果後再繼續;
- 實際中儘量在 EventTask 中執行耗時操作,減少在 MicroTask 中執行;若任務耗時時間較長可以嘗試用 isolate 開啟新的非同步執行緒執行;
小菜對任務排程的理解還不夠深入,如有錯誤請多多指導!
來源: 阿策小和尚