Flutter 異步機制:microtask

以前說到 Flutter 中與異步相關的 Future,瞭解到,dart 是一個單線程模型的語言,當咱們使用 Future 去實現一個異步操做時,多半是利用單線程的事件循環機制來模擬,相似於 Android 中的 Looper。c++

不過 dart 中的事件循環其實仍是分兩種的,也就是網上常說的兩個循環隊列,microtask queue 和 event queue,在介紹 Future 的那篇文章裏面其實主要說到的是 event queue 這種模式的運做方式,因此打算在此再詳細說下 microtask queue 的一些實現細節,以此來講明,爲什麼 microtask queue 必定會優先於 event queue 執行。markdown

就拿 Future 來講,它就能夠支持跑在兩種隊列上,當咱們直接調用 Future()Future.delayed() 的時候,內部都是經過 Timer 實現的,其內部就會利用 ReceivePort/SendPort 進行一次事件循環,在下一次循環時調用對應的 callback,具體的調用過程在 Future 那篇裏面有說。而後,當咱們調用Future.microtask建立一個 Future 時,此時它的 callback 就是被添加到 microtask 隊列上的,那麼 callback 的調用時機就會被提早,下面就簡單看下一個 microtask 的執行過程。異步

建立任務

首先,當咱們使用 Future 建立一個 microtask 時,調用的方法爲:async

// 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 上的。ide

// 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 初始化時,會依此調用oop

  1. DartIsolate::LoadLibraries
  2. DartRuntimeHooks::Install
  3. InitDartAsync

_setScheduleImmediateClosure 就是在這裏被調用的,ui

// 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 找到函數定義,this

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。spa

執行任務

接着看 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 執行時機

從當前的信息瞭解到,在一個連續的代碼中,能夠有三種形式的代碼調用,

  1. 直接調用
  2. 經過 microtask 調用
  3. 經過事件隊列調用

這三種形式的調用,其執行前後爲直接調用->microtask->事件隊列, 從以前的分析中就很容易理解了,下面再總結一下。

首先,直接調用很好理解,他們都是同在一個函數下,按順序調用。

而後,microtask 的調用是發生在當前的消息事件調用以後(從 _handleMessage 實現可知),而從以前的分析中得知,全部的關於 dart 代碼的調用,其入口都是 _handleMessage,直接執行 main 函數的時候是,新啓動一個 isolate 也是,而 _handleMessage 下有兩個入口,一個是 handler,一個是 _runPendingImmediateCallback,而無論 microtask 是在這兩個函數的哪個中建立的,都會在本次 _handleMessage 調用過程當中執行。

而第三種,消息事件調用就不同了,它必定是要等到下次甚至好幾回以後的 _handleMessage 中才會調用,相比之下,它的調用很昂貴,須要通過 dart vm 的一層處理才能調用到。

總結,基於 dart 的單線程事件循環模型,咱們能夠將 _handleMessage 看做一次事件循環,那麼「直接調用」與「microtask」都是在當次 _handleMessage 的調用中就會調用到,而「事件隊列調用」則至少要等到下次 _handleMessage 調用,由此決定了這三種代碼調用的執行順序。

相關文章
相關標籤/搜索