之前說到 Flutter 中與非同步相關的 Future,瞭解到,dart 是一個單執行緒模型的語言,當我們使用 Future 去實現一個非同步操作時,多半是利用單執行緒的事件迴圈機制來模擬,類似於 Android 中的 Looper。
不過 dart 中的事件迴圈其實還是分兩種的,也就是網上常說的兩個迴圈佇列,microtask queue 和 event queue,在介紹 Future 的那篇文章裡面其實主要說到的是 event queue 這種模式的運作方式,所以打算在此再詳細說下 microtask queue 的一些實現細節,以此來說明,為何 microtask queue 一定會優先於 event queue 執行。
就拿 Future 來說,它就可以支援跑在兩種佇列上,當我們直接呼叫 Future()
或 Future.delayed()
的時候,內部都是通過 Timer
實現的,其內部就會利用 ReceivePort/SendPort 進行一次事件迴圈,在下一次迴圈時呼叫對應的 callback,具體的呼叫過程在 Future 那篇裡面有說。然後,當我們呼叫Future.microtask
建立一個 Future 時,此時它的 callback 就是被新增到 microtask 佇列上的,那麼 callback 的呼叫時機就會被提前,下面就簡單看下一個 microtask 的執行過程。
建立任務
首先,當我們使用 Future 建立一個 microtask 時,呼叫的方法為:
// sdk/lib/async/future.dart
factory Future.microtask(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
scheduleMicrotask(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
// sdk/lib/async/schedule_microtask.dart
@pragma('vm:entry-point', 'call')
void scheduleMicrotask(void Function() callback) {
_Zone currentZone = Zone._current;
if (identical(_rootZone, currentZone)) {
// No need to bind the callback. We know that the root's scheduleMicrotask
// will be invoked in the root zone.
_rootScheduleMicrotask(null, null, _rootZone, callback);
return;
}
_ZoneFunction implementation = currentZone._scheduleMicrotask;
if (identical(_rootZone, implementation.zone) &&
_rootZone.inSameErrorZone(currentZone)) {
_rootScheduleMicrotask(
null, null, currentZone, currentZone.registerCallback(callback));
return;
}
Zone.current.scheduleMicrotask(Zone.current.bindCallbackGuarded(callback));
}
複製程式碼
結構跟呼叫 Timer 基本一致,這裡主要是呼叫 scheduleMicrotask 去建立一個 microtask 的;在 scheduleMicrotask 中根據 Zone 型別不同,選擇了不同的方式建立 microtask,一般情況下,就直接看 RootZone 的實現就可以了,預設情況下,程式碼就是跑在 RootZone 上的。
// sdk/lib/async/zone.dart
void _rootScheduleMicrotask(
Zone? self, ZoneDelegate? parent, Zone zone, void f()) {
if (!identical(_rootZone, zone)) {
bool hasErrorHandler = !_rootZone.inSameErrorZone(zone);
if (hasErrorHandler) {
f = zone.bindCallbackGuarded(f);
} else {
f = zone.bindCallback(f);
}
}
_scheduleAsyncCallback(f);
}
// sdk/lib/async/schedule_microtask.dart
void _scheduleAsyncCallback(_AsyncCallback callback) {
_AsyncCallbackEntry newEntry = new _AsyncCallbackEntry(callback);
_AsyncCallbackEntry? lastCallback = _lastCallback;
if (lastCallback == null) {
_nextCallback = _lastCallback = newEntry;
if (!_isInCallbackLoop) {
_AsyncRun._scheduleImmediate(_startMicrotaskLoop);
}
} else {
lastCallback.next = newEntry;
_lastCallback = newEntry;
}
}
複製程式碼
之後呼叫到 _scheduleAsyncCallback,這個函式是比較重要的,_nextCallback 就是 microtask queue 的的隊首,_lastCallback 則是隊尾,這個函式的作用就是將新的 callback 新增到 microtask 佇列中,同時,當此前還沒有新增過 microtask 的時候,就需要呼叫_AsyncRun._scheduleImmediate(_startMicrotaskLoop);
開始一輪 microtask 的執行,_scheduleImmediate 是一個 external 函式,其實現在 sdk/lib/_internal/vm/lib/schedule_microtask_patch.dart 中,
@patch
class _AsyncRun {
@patch
static void _scheduleImmediate(void callback()) {
final closure = _ScheduleImmediate._closure;
if (closure == null) {
throw new UnsupportedError("Microtasks are not supported");
}
closure(callback);
}
}
typedef void _ScheduleImmediateClosure(void callback());
class _ScheduleImmediate {
static _ScheduleImmediateClosure? _closure;
}
@pragma("vm:entry-point", "call")
void _setScheduleImmediateClosure(_ScheduleImmediateClosure closure) {
_ScheduleImmediate._closure = closure;
}
複製程式碼
這裡的大致流程就是,給 _scheduleImmediate 傳一個 callback,也就是 _startMicrotaskLoop 函式,不過 _scheduleImmediate 也只是轉手將 callback 給了 _ScheduleImmediate._closure 執行,但是 _ScheduleImmediate._closure 是通過 _setScheduleImmediateClosure 賦值的,所以這裡還需要再看 _setScheduleImmediateClosure 是何時被呼叫。從宣告看,這個函式應該是要在 dart vm 中呼叫的,在 vm 程式碼中搜尋找到,在進行 isolate 初始化時,會依此呼叫
DartIsolate::LoadLibraries
DartRuntimeHooks::Install
InitDartAsync
_setScheduleImmediateClosure 就是在這裡被呼叫的,
// dart_runtime_hooks.cc
static void InitDartAsync(Dart_Handle builtin_library, bool is_ui_isolate) {
Dart_Handle schedule_microtask;
if (is_ui_isolate) {
schedule_microtask =
InvokeFunction(builtin_library, "_getScheduleMicrotaskClosure");
} else {
Dart_Handle isolate_lib = Dart_LookupLibrary(ToDart("dart:isolate"));
Dart_Handle method_name =
Dart_NewStringFromCString("_getIsolateScheduleImmediateClosure");
schedule_microtask = Dart_Invoke(isolate_lib, method_name, 0, NULL);
}
Dart_Handle async_library = Dart_LookupLibrary(ToDart("dart:async"));
Dart_Handle set_schedule_microtask = ToDart("_setScheduleImmediateClosure");
Dart_Handle result = Dart_Invoke(async_library, set_schedule_microtask, 1,
&schedule_microtask);
PropagateIfError(result);
}
複製程式碼
在這裡,給 set_schedule_microtask 傳的引數時 schedule_microtask,這個函式則是來自於名為 _getIsolateScheduleImmediateClosure 的函式,且這就是一個 dart 函式,直接搜尋,便可以在 sdk/lib/_internal/vm/lib/isolate_patch.dart 找到函式定義,
void _isolateScheduleImmediate(void callback()) {
assert((_pendingImmediateCallback == null) ||
(_pendingImmediateCallback == callback));
_pendingImmediateCallback = callback;
}
@pragma("vm:entry-point", "call")
void _runPendingImmediateCallback() {
final callback = _pendingImmediateCallback;
if (callback != null) {
_pendingImmediateCallback = null;
callback();
}
}
/// The embedder can execute this function to get hold of
/// [_isolateScheduleImmediate] above.
@pragma("vm:entry-point", "call")
Function _getIsolateScheduleImmediateClosure() {
return _isolateScheduleImmediate;
}
複製程式碼
從而得知,_ScheduleImmediate._closure 就是 _isolateScheduleImmediate,所以,callback(也就是 _startMicrotaskLoop)最後作為 _isolateScheduleImmediate 的引數呼叫,也就是把它賦值給 _pendingImmediateCallback。
執行任務
接著看 microtask 的執行,從上面得知,_pendingImmediateCallback 就是 _startMicrotaskLoop,而且 _pendingImmediateCallback 在 _runPendingImmediateCallback 函式中被呼叫,也就是說,當 _runPendingImmediateCallback 被呼叫時,便會啟動新一輪 microtask 的執行。
看到 _runPendingImmediateCallback 這個名字是否有點眼熟,之前在介紹 Future 的時候,通過檢視程式碼,瞭解到 dart vm 中處理訊息事件,最終會呼叫 dart 中的 _handleMessage 函式,
@pragma("vm:entry-point", "call")
static void _handleMessage(Function handler, var message) {
// TODO(floitsch): this relies on the fact that any exception aborts the
// VM. Once we have non-fatal global exceptions we need to catch errors
// so that we can run the immediate callbacks.
handler(message);
_runPendingImmediateCallback();
}
複製程式碼
在 dart 中,所有的程式碼其實都是通過這裡呼叫的,不管是當我們啟動一個 Isolate,還是通過 Future 執行非同步呼叫。比如當我們直接啟動 dart 時,它會在 vm 中先建立好一個 isolate,然後執行它的 entry point,對於 root isolate,它的 entry point 就是 main,對於自定義的 isolate,它的 entry point 就是外部傳入的函式,而執行 entry point 的方式,就是通過事件佇列,可以看下面這段程式碼:
@pragma("vm:entry-point", "call")
void _startIsolate(
Function entryPoint, List<String>? args, Object? message, bool isSpawnUri) {
_delayEntrypointInvocation(entryPoint, args, message, isSpawnUri);
}
void _delayEntrypointInvocation(Function entryPoint, List<String>? args,
Object? message, bool allowZeroOneOrTwoArgs) {
final port = RawReceivePort();
port.handler = (_) {
port.close();
if (allowZeroOneOrTwoArgs) {
if (entryPoint is _BinaryFunction) {
(entryPoint as dynamic)(args, message);
} else if (entryPoint is _UnaryFunction) {
(entryPoint as dynamic)(args);
} else {
entryPoint();
}
} else {
entryPoint(message);
}
};
port.sendPort.send(null);
}
複製程式碼
當 isolate 在 vm 中建立好後,c++ 就會呼叫 _startIsolate 啟動 isolate,而在這個函式中,它依舊是通過 ReceivePort/SendPort 向事件佇列中傳送一個事件,以此來執行 entryPoint。
以上文字,僅為說明 _handleMessage 這個函式的呼叫時機,同時也是在說明 _runPendingImmediateCallback 的呼叫時機,也就是 _startMicrotaskLoop。
// sdk/lib/async/schedule_microtask.dart
void _startMicrotaskLoop() {
_isInCallbackLoop = true;
try {
// Moved to separate function because try-finally prevents
// good optimization.
_microtaskLoop();
} finally {
_lastPriorityCallback = null;
_isInCallbackLoop = false;
if (_nextCallback != null) {
_AsyncRun._scheduleImmediate(_startMicrotaskLoop);
}
}
}
void _microtaskLoop() {
for (var entry = _nextCallback; entry != null; entry = _nextCallback) {
_lastPriorityCallback = null;
var next = entry.next;
_nextCallback = next;
if (next == null) _lastCallback = null;
(entry.callback)();
}
}
複製程式碼
這個函式的實現還是挺簡單的,就是直接遍歷 microtask 佇列去執行,有一點,當我們在 micro task 執行過程中再建立 microtask 的時候,由於此時 _microtaskLoop 還未結束,所以當這個 microtask 執行完之後,會繼續執行新加的 microtask。不過在 _startMicrotaskLoop 中執行 _microtaskLoop 外面加了一個 try...finally 倒是沒太理解這裡的用途,因為從這裡直到 _handleMessage 都沒見到捕獲異常的操作。
總的來說,以上就是 microtask 從建立到執行的過程,下面具體講講幾種不同的程式碼塊具體的執行時機。
microtask 執行時機
從當前的資訊瞭解到,在一個連續的程式碼中,可以有三種形式的程式碼呼叫,
- 直接呼叫
- 通過 microtask 呼叫
- 通過事件佇列呼叫
這三種形式的呼叫,其執行先後為直接呼叫->microtask->事件佇列, 從之前的分析中就很容易理解了,下面再總結一下。
首先,直接呼叫很好理解,他們都是同在一個函式下,按順序呼叫。
然後,microtask 的呼叫是發生在當前的訊息事件呼叫之後(從 _handleMessage 實現可知),而從之前的分析中得知,所有的關於 dart 程式碼的呼叫,其入口都是 _handleMessage,直接執行 main 函式的時候是,新啟動一個 isolate 也是,而 _handleMessage 下有兩個入口,一個是 handler,一個是 _runPendingImmediateCallback,而不管 microtask 是在這兩個函式的哪一個中建立的,都會在本次 _handleMessage 呼叫過程中執行。
而第三種,訊息事件呼叫就不一樣了,它一定是要等到下次甚至好幾次之後的 _handleMessage 中才會呼叫,相比之下,它的呼叫很昂貴,需要經過 dart vm 的一層處理才能呼叫到。
總結,基於 dart 的單執行緒事件迴圈模型,我們可以將 _handleMessage 看作一次事件迴圈,那麼「直接呼叫」與「microtask」都是在當次 _handleMessage 的呼叫中就會呼叫到,而「事件佇列呼叫」則至少要等到下次 _handleMessage 呼叫,由此決定了這三種程式碼呼叫的執行順序。