我們在昨天的文章裡提到,每一個Isolate只會有單一執行緒,而我們在這個執行緒上所有的異步處理,都是靠Event Loop機制來完成的。今天我們就繼續來看看這個Event Loop到底是怎麼運作的。
當我們啟動一個Dart應用程式時,Dart會進行幾個步驟:
Microtask
和Event
兩個FIFO Queuemain()
函數main()
執行結束後,啟動Event Loop我們可以看到Dart會先處理完所有的Microtask,再開始處理Event。Microtask到底是什麼?為什麼這麼重要?
Microtask是我們平常很少會接觸到的東西。簡單來說,它扮演的角色就相當於是一個Event的onComplete
,主要應用是在下一個Event開始之前,進行一些這一個Event的清理動作,例如通知更新,關閉/釋放資源等等。如果有須要,我們可以呼叫scheduleMicrotask
來將task排入Microtask Queue。不過實際上在編寫Flutter應用程式時,我們很少會須要以Event為單位來進行這些清理,通常都是隨著Widget Lifecycle進行。即使是在整個Flutter專案中,使用到scheduleMicrotask的地方也不多:
來看看其中幾個例子:
Event對我們來說就相對熟悉了,像是各種I/O、手勢、計時器、Vsync、來自其它Isolate的訊息,所有Future和async function的呼叫,都會被加入Event Queue裡。值得注意的是,這是一個FIFO Queue,代表Event之間沒有任何權重關係,我們也沒有辦法直接把Event插隊到Queue中間,更不可能打斷現在正在執行的Event。也就是說,當我們執行
Future.delayed(Duration(seconds: 5), (){ print("Hello"); });
它並不會剛好在5秒後執行,只是會在5秒後被加入Event Queue而已。那時候可能Event Loop正在執行一個長時間的Event,或它前面已經排了很多其它Event。
首先我們來看看怎麼建立Future。雖然Dart提供了幾種建立Future的方式:
Future(() { });
Future.delayed();
Future.value();
Future.sync(() => null)
Future.microtask(() => null);
但其中只有Future()
和Future.delayed()
會被加入Event Queue,其它三個都是直接進入Microtask Queue。有趣的是,Future()和Future.delayed()其實都是靠Timer
來實作的,只是包上了一些錯誤處理的try/catch而已。
factory Future(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
Timer.run(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
建立Future,並將它加入Event Loop之後,我們該怎麼在它執行完畢時,得到結果並進行後續處理呢?當然現在我們可以使用await語法,不過再這之前,我們主要是透過Future的三個callback:
Future(() => "Hello")
.then((value) => value + "World")
.then((value) => print(value))
.catchError((error) { print(error); })
.whenComplete(() => print("Completed!"));
這些callback會在Future完成後立刻被呼叫,而不會加入Event Queue中。也就是說不論我們串了多少then
,它們全部會在同一個Event中依序被執行。但如果在我們串接then之前,Future就已經完成了,這時候then會被加入MicroTask Queue,以確保它在下個Event開始之前完成。
接著我們來看看async函數實際上是怎麼被加入Event Loop的:
void main() {
Future(() {
print("long event start");
DateTime end = DateTime.now().add(Duration(seconds: 5));
while (DateTime.now().isBefore(end));
print("long event end");
});
asyncFunction();
}
Future<void> asyncFunction() async {
print("async function start");
await Future(() {
print("async function await start");
DateTime end = DateTime.now().add(Duration(seconds: 5));
while (DateTime.now().isBefore(end));
print("async function await end");
});
print("async function end");
}
如果我們執行這段程式碼,得到的結果會是:
I/flutter (10076): async function start
I/flutter (10076): long event start
I/flutter (10076): long event end
I/flutter (10076): async function await start
I/flutter (10076): async function await end
I/flutter (10076): async function end
程式執行順序如下:
print("async function start")
第四點是瞭解async/await機制最重要的一點。當我們呼叫一個async函數時,並不是把它整個加入Event Loop然後繼續執行,而是先執行到第一個await之前,剩下的才加入Event Loop。如果同一段程式碼裡面有好幾個await呢?
Future<void> asyncFunction() async {
print("start");
await Future(() {});
print("future 1 finished");
await Future(() {});
print("future 2 finished");
await Future(() {});
print("future 3 finished");
}
當然,每次程式執行到await就會中斷,把剩下的所有程式碼丟進Event Loop,然後繼續執行目前的Event。
我們一開始說明了Dart程式的啟動流程,瞭解了Event Loop中Microtask和Event Queue各自的任務和運作機制,最後也看到了我們平常熟悉的Future/async/await,它們背後的Event Queue運作細節。到這裡希望你有更瞭解整個Event Loop的運作機制了,下次當你被程式碼中層層疊疊的Future/async/await搞得暈頭轉向,不知道它們到底會如何執行時,只要想想它們是怎麼被加入Event Loop的,相信一切就會豁然開朗了。