In Flutter, asynchronous tasks are mainly realized through Timer and microtask. In this article, the principle of Flutter Timer is described to realize asynchronous tasks through the Timer. In this article, we will look at another implementation of asynchronous tasks, the use of microtasks and their implementation principle.

1. Use of microtasks

Let’s look at the use of microtasks. The code is very simple, as follows.

A / / usage
Future.microtask(() {
  print("microtask1");
});
2 / / usage
scheduleMicrotask(() {
  print("microtask2");
});
3 / / usage
Zone.current.scheduleMicrotask((){
  print("microtask3");
});
4 / / usage
Zone.root.scheduleMicrotask((){
  print("microtask4");
});
Copy the code

So that’s all you can do with microtasks. Basically, the first two usages are more common, but the first two usages are only the encapsulation of the latter two usages. Let’s take a look at how microtasks work, but before we look at how microtasks work, we need to understand how UI threads are created.

2. UI thread creation

In the Engine startup process of Flutter, it is mentioned that UI, IO, and GPU threads are created during Engine creation, but I did not go into detail. This article uses the Android platform as an example to learn more about how THE UI thread is created in Flutter (IO thread, GPU thread and UI thread are all of the same type of object, but named differently).

[-> flutter/shell/platform/android/android_shell_holder.cc]

AndroidShellHolder::AndroidShellHolder(
    flutter::Settings settings,
    fml::jni::JavaObjectWeakGlobalRef java_object,
    bool is_background_view)
    : settings_(std::move(settings)), java_object_(java_object) {
  static size_t shell_count = 1;
  auto thread_label = std::to_string(shell_count++);
            
  // Create the target thread
  if (is_background_view) {
    // Create only UI threads
    thread_host_ = {thread_label, ThreadHost::Type::UI};
  } else {
    // Create UI thread, GPU thread, and IO threadthread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::GPU | ThreadHost::Type::IO}; }... }Copy the code

Here thread_host_ is a structure, so look directly at the implementation in that structure.

[-> flutter/shell/common/thread_host.cc]

#include "flutter/shell/common/thread_host.h"

namespace flutter {

...

ThreadHost::ThreadHost(std: :string name_prefix, uint64_t mask) {
  if (mask & ThreadHost::Type::Platform) {
    Platform thread is the main thread in Android, so the platform_thread named xxxx. Platform is not created
    platform_thread = std::make_unique<fml::Thread>(name_prefix + ".platform");
  }

  if (mask & ThreadHost::Type::UI) {
    // Create a UI thread
    ui_thread = std::make_unique<fml::Thread>(name_prefix + ".ui");
  }

  if (mask & ThreadHost::Type::GPU) {
    // Create a GPU thread
    gpu_thread = std::make_unique<fml::Thread>(name_prefix + ".gpu");
  }

  if (mask & ThreadHost::Type::IO) {
    // Create the IO thread
    io_thread = std::make_unique<fml::Thread>(name_prefix + ".io"); }}... }Copy the code

As can be seen from the above, UI thread, IO thread and GPU thread are the same type of object, but named differently. So let’s look at the implementation of the UI thread.

[-> flutter/fml/thread.cc]

Thread::Thread(const std: :string& name) : joined_(false) {
  fml::AutoResetWaitableEvent latch;
  fml::RefPtr<fml::TaskRunner> runner;
  // Create a Thread object
  thread_ = std::make_unique<std::thread>([&latch, &runner, name]() -> void {
    // Set the name of the current thread. Since this is the creation of the UI thread, the name is xxx.ui
    SetCurrentThreadName(name);
    Create a MessageLoop object
    fml::MessageLoop::EnsureInitializedForCurrentThread();
    // Get the loop corresponding to MessageLoop
    auto& loop = MessageLoop::GetCurrent();
    runner = loop.GetTaskRunner();
    / / wake
    latch.Signal();
    / / run loop
    loop.Run();
  });
  / / wait for
  latch.Wait();
  task_runner_ = runner;
}
Copy the code

In the Thread constructor, a new Thread is created. UI, XXxxX. gpu, and xxxxx. IO are named for the created thread. The thread is also given a MessageLoop object, and then the MessageLoop run function is executed, even if the MessageLoop runs.

Here we focus on the creation of the MessageLoop object, which is implemented as follows.

[-> flutter/fml/message_loop.cc]

//tls_message_loop is similar to ThreadLocal in Java to ensure that MessageLoop belongs to only one thread and cannot be accessed by other threads
FML_THREAD_LOCAL ThreadLocalUniquePtr<MessageLoop> tls_message_loop;

void MessageLoop::EnsureInitializedForCurrentThread() {
  // Ensure that there is only one MessageLoop object per process
  if(tls_message_loop.get() ! =nullptr) {
    // Already initialized.
    return;
  }
  tls_message_loop.reset(new MessageLoop());
}

// Create a MessageLoop object
MessageLoop::MessageLoop()
      // Create the MessageLoopImpl object
    : loop_(MessageLoopImpl::Create()),
      // Create the TaskRunner object
      task_runner_(fml::MakeRefCounted<fml::TaskRunner>(loop_)) {}
Copy the code

The emphasis here is on loop_. It is a MessageLoopImpl object, and the specific implementation of MessageLoopImpl varies from platform to platform. Using Android as an example, the Create method creates a MessageLoopAndroid object that inherits from MessageLoopImpl.

[-> flutter/fml/platform/android/message_loop_android.cc]

static constexpr int kClockType = CLOCK_MONOTONIC;

static ALooper* AcquireLooperForThread(a) {
  ALooper* looper = ALooper_forThread();

  if (looper == nullptr) {
    // If no looper exists in the current thread, a new looper is created
    looper = ALooper_prepare(0);
  }

  // If the current thread has a looper, get its reference and return it
  ALooper_acquire(looper);
  return looper;
}

// constructor
MessageLoopAndroid::MessageLoopAndroid()
      // Create a looper object
    : looper_(AcquireLooperForThread()),
      timer_fd_(::timerfd_create(kClockType, TFD_NONBLOCK | TFD_CLOEXEC)),
      running_(false) {

  static const int kWakeEvents = ALOOPER_EVENT_INPUT;
  
  // Execute the callback method
  ALooper_callbackFunc read_event_fd = [](int.int events, void* data) -> int {
    if (events & kWakeEvents) {
      reinterpret_cast<MessageLoopAndroid*>(data)->OnEventFired();
    }
    return 1;  // continue receiving callbacks
  };

  int add_result = ::ALooper_addFd(looper_.get(),          // looper
                                   timer_fd_.get(),        // fd
                                   ALOOPER_POLL_CALLBACK,  // ident
                                   kWakeEvents,            // events
                                   read_event_fd,          // callback
                                   this                    // baton
  );
}

MessageLoopAndroid::~MessageLoopAndroid() {
  int remove_result = ::ALooper_removeFd(looper_.get(), timer_fd_.get());
  FML_CHECK(remove_result == 1);
}

/ / the operation of the stars
void MessageLoopAndroid::Run() {
  FML_DCHECK(looper_.get() == ALooper_forThread());

  running_ = true;

  while (running_) {
    // Wait for the event to execute
    int result = ::ALooper_pollOnce(- 1.// infinite timeout
                                    nullptr.// out fd,
                                    nullptr.// out events,
                                    nullptr   // out data
    );
    if (result == ALOOPER_POLL_TIMEOUT || result == ALOOPER_POLL_ERROR) {
      // This handles the case where the loop is terminated using ALooper APIs.
      running_ = false; }}}// Terminates event execution
void MessageLoopAndroid::Terminate() {
  running_ = false;
  ALooper_wake(looper_.get());
}

// Wake up the execution of events
void MessageLoopAndroid::WakeUp(fml::TimePoint time_point) {
  bool result = TimerRearm(timer_fd_.get(), time_point);
  FML_DCHECK(result);
}

// Listen for the Loop callback
void MessageLoopAndroid::OnEventFired() {
  if(TimerDrain(timer_fd_.get())) { RunExpiredTasksNow(); }}Copy the code

The above code implements an asynchronous IO using ALooper. In Android, ALooper can be thought of as a wrapper around Looper, that is, operating Looper through ALooper.

Note: ALooper to operate Looper refers to Native Looper, not framework Looper.

When the MessageLoopAndroid object is successfully created, the run function of the object is called to make the task processing in the UI thread run. The UI thread has been successfully created and initialized.

This is how UI threads are created on Android. On other platforms, UI threads are created similarly, with the exception of asynchronous IO. For example, in iOS, asynchronous IO is implemented by CFRunLoop.

3. Realization principle of micro-task

Back to the implementation of microtasks. Take scheduleMicrotask method as an example to see its code implementation.

void scheduleMicrotask(void callback()) {
  _Zone currentZone = Zone.current;
  // Check whether the current Zone and _rootZone are the same Zone object.
  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; }... }Copy the code

Most custom zones do not come from the implementation of the scheduleMicrotask method, so the scheduleMicrotask method of a custom Zone ultimately calls the _rootScheduleMicrotask method. Let’s look at the implementation of this method.

void _rootScheduleMicrotask(
    Zone self, ZoneDelegate parent, Zone zone, void f()) {
  ...
  _scheduleAsyncCallback(f);
}
Copy the code

The above code is very simple, just call the _scheduleAsyncCallback method, and then look at the implementation of this method.

// The node is the head of the single-linked list of _AsyncCallbackEntry
_AsyncCallbackEntry _nextCallback;
// The node is the last node of the single-linked list of _AsyncCallbackEntry
_AsyncCallbackEntry _lastCallback;

 // Priority callback methods are placed at the head of the list. If there are more than one, they are sorted in order of addition
_AsyncCallbackEntry _lastPriorityCallback;

// Whether the callback method is currently being executed
bool _isInCallbackLoop = false;

// Iterate through the list and execute the corresponding callback method
void _microtaskLoop() {
  while(_nextCallback ! =null) {
    _lastPriorityCallback = null;
    _AsyncCallbackEntry entry = _nextCallback;
    _nextCallback = entry.next;
    if (_nextCallback == null) _lastCallback = null; (entry.callback)(); }}// Start executing the callback method
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); }}}// Add the callback method to the list
void _scheduleAsyncCallback(_AsyncCallback callback) {
  _AsyncCallbackEntry newEntry = new _AsyncCallbackEntry(callback);
  if (_nextCallback == null) {
    _nextCallback = _lastCallback = newEntry;
    // If the callback method is not currently processed
    if (!_isInCallbackLoop) {
      _AsyncRun._scheduleImmediate(_startMicrotaskLoop);
    }
  } else{ _lastCallback.next = newEntry; _lastCallback = newEntry; }}void _schedulePriorityAsyncCallback(_AsyncCallback callback) {
  if (_nextCallback == null) {
    _scheduleAsyncCallback(callback);
    _lastPriorityCallback = _lastCallback;
    return;
  }
  _AsyncCallbackEntry entry = new _AsyncCallbackEntry(callback);
  if (_lastPriorityCallback == null) {
    entry.next = _nextCallback;
    _nextCallback = _lastPriorityCallback = entry;
  } else {
    entry.next = _lastPriorityCallback.next;
    _lastPriorityCallback.next = entry;
    _lastPriorityCallback = entry;
    if (entry.next == null) { _lastCallback = entry; }}}Copy the code

In the _scheduleAsyncCallback method, the microtask to be executed is wrapped into a _AsyncCallbackEntry object and added to the list, meaning that all microtasks are a node in the list. A microtask is executed when iterating through the list and executing the callback method in the node.

Here to pay attention to the _schedulePriorityAsyncCallback method, it is also the micro tasks as a _AsyncCallbackEntry object and added to the list. But the microtasks here have a higher priority and are added directly to the head of the list. There are only in the current Zone object handleUncaughtError method will call _schedulePriorityAsyncCallback, namely capture error priority are higher than normal micro task priority.

Once the linked list is created, you need to be able to traverse the list at the appropriate time, which is when the _scheduleImmediate method is executed.

class _AsyncRun {
  // Callback corresponds to the _startMicrotaskLoop method
  external static void _scheduleImmediate(void callback());
}
Copy the code

3.1. Microtask set

Look at the _scheduleImmediate method implementation, implementation code is very simple, as follows.

@patch
class _AsyncRun {
  @patch
  static void _scheduleImmediate(void callback()) {
    if (_ScheduleImmediate._closure == null) {
      throw new UnsupportedError("Microtasks are not supported"); } _ScheduleImmediate._closure(callback); }}typedef void _ScheduleImmediateClosure(void callback());

class _ScheduleImmediate {
  static _ScheduleImmediateClosure _closure;
}

// called when Engine is initialized
@pragma("vm:entry-point"."call")
void _setScheduleImmediateClosure(_ScheduleImmediateClosure closure) {
  _ScheduleImmediate._closure = closure;
}

@pragma("vm:entry-point"."call")
void _ensureScheduleImmediate() {
  _AsyncRun._scheduleImmediate(_startMicrotaskLoop);
}
Copy the code

Since _setScheduleImmediateClosure is in RootIsolate create success after InitDartAsync function calls, so to see InitDartAsync function implementation.

[-> flutter/lib/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 =
        GetFunction(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);
}
Copy the code

The code is simple, with different implementations depending on the ISOLATE. First to see ui_isolate, namely RootIsolate, in RootIsolate, assign _ScheduleImmediate. _closure value is _getScheduleMicrotaskClosure method, so to see the implementation of the method.

void _scheduleMicrotask(void callback()) native 'ScheduleMicrotask';

@pragma('vm:entry-point')
Function _getScheduleMicrotaskClosure() => _scheduleMicrotask; 
Copy the code

The above code is simple and corresponds to the ScheduleMicrotask function.

[-> flutter/lib/ui/dart_runtime_hooks.cc]

void ScheduleMicrotask(Dart_NativeArguments args) {
  Dart_Handle closure = Dart_GetNativeArgument(args, 0);
  UIDartState::Current()->ScheduleMicrotask(closure);
}
Copy the code

The passed parameters are resolved in the ScheduleMicrotask function, and then the ScheduleMicrotask function of the current UIDartState object is called.

[-> flutter/lib/ui/ui_dart_state.cc]

void UIDartState::ScheduleMicrotask(Dart_Handle closure) {
  if(tonic::LogIfError(closure) || ! Dart_IsClosure(closure)) {return;
  }

  microtask_queue_.ScheduleMicrotask(closure);
}
Copy the code

Within the ScheduleMicrotask function of the UIDartState object, the ScheduleMicrotask function of the DartMicrotaskQueue object is called.

[-> tonic/dart_microtask_queue.cc]

void DartMicrotaskQueue::ScheduleMicrotask(Dart_Handle callback) {
  queue_.emplace_back(DartState::Current(), callback);
}
Copy the code

Finally, you get to the DartMicrotaskQueue object, where there is a collection called Queue_ to which the ScheduleMicrotask function adds the callback. That is, in RootIsolate, the _startMicrotaskLoop method is finally added as a parameter to the collection Queue_.

The treatment of non ui_isolate again, in the ui_isolate, assign _ScheduleImmediate. Becomes _getIsolateScheduleImmediateClosure _closure value method. The implementation of this method is much simpler, as shown in the following code.

_ImmediateCallback _pendingImmediateCallback;

void _isolateScheduleImmediate(void callback()) {
  _pendingImmediateCallback = callback;
}

@pragma("vm:entry-point"."call")
Function _getIsolateScheduleImmediateClosure() {
  return _isolateScheduleImmediate;
}
Copy the code

In the above code, only the _pendingImmediateCallback is assigned, i.e. the _startMicrotaskLoop method is assigned as a value to the _pendingImmediateCallback.

3.2 Execution of microtasks

With this preparation in place, you can now execute the _startMicrotaskLoop method when appropriate to handle all microtasks.

There are also UI_ISOLATE and non-UI_ISOLATE. First, let’s see if the current ISOLATE is UI_ISOLATE.

In addition to adding _startMicrotaskLoop to the set, the _startMicrotaskLoop method is also executed in the UIDartState object in FlushMicrotasksNow.

[-> flutter/lib/ui/ui_dart_state.cc]

void UIDartState::FlushMicrotasksNow() {
  microtask_queue_.RunMicrotasks();
}
Copy the code

Looking at the implementation of RunMicrotasks, the code is as follows.

[-> tonic/dart_microtask_queue.cc]

void DartMicrotaskQueue::RunMicrotasks() { while (! queue_.empty()) { MicrotaskQueue local; std::swap(queue_, local); // Iterate over all elements in the collection for (const auto& callback: local) { if (auto dart_state = callback.dart_state().lock()) { DartState::Scope dart_scope(dart_state.get()); // Call the _startMicrotaskLoop method Callback.value () corresponds to _startMicrotaskLoop Dart_Handle result = Dart_InvokeClosure(callback.value(), 0, nullptr); . }}}}Copy the code

Now you know how microtasks are added to the collection and executed in UI_ISOLATE. So when is the timing of the micromission? That’s when the FlushMicrotasksNow function is called.

After viewing the Flutter source code. FlushMicrotasksNow is called only in the Window BeginFrame function and the UIDartState AddOrRemoveTaskObserver function. So let’s start with the implementation of BeginFrame.

[-> flutter/lib/ui/window/window.cc]

void Window::BeginFrame(fml::TimePoint frameTime) {
  std: :shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
  if(! dart_state)return;
  tonic::DartState::Scope scope(dart_state);

  int64_t microseconds = (frameTime - fml::TimePoint()).ToMicroseconds();
  // Call the _beginFrame method to begin drawing
  tonic::LogIfError(tonic::DartInvokeField(library_.value(), "_beginFrame",
                                           {
                                               Dart_NewInteger(microseconds),
                                           }));
  // Perform all microtasks
  UIDartState::Current()->FlushMicrotasksNow();
  // Call _drawFrame to draw the UI
  tonic::LogIfError(tonic::DartInvokeField(library_.value(), "_drawFrame", {}));
}
Copy the code

The code is simple, but it also shows that the window calls to Flutter between the _beginFrame and _drawFrame methods will remove all microtasks. You can’t do time-consuming operations in microtasks that would affect UI rendering.

Look again at the AddOrRemoveTaskObserver function.

[-> flutter/lib/ui/ui_dart_state.cc]

UIDartState::UIDartState(
    TaskRunners task_runners,
    TaskObserverAdd add_callback,
    TaskObserverRemove remove_callback,
    fml::WeakPtr<SnapshotDelegate> snapshot_delegate,
    fml::WeakPtr<IOManager> io_manager,
    fml::RefPtr<SkiaUnrefQueue> skia_unref_queue,
    fml::WeakPtr<ImageDecoder> image_decoder,
    std: :string advisory_script_uri,
    std: :string advisory_script_entrypoint,
    std: :string logger_prefix,
    UnhandledExceptionCallback unhandled_exception_callback,
    std: :shared_ptr<IsolateNameServer> isolate_name_server)
    : task_runners_(std::move(task_runners)),
      // Assign add_callback_
      add_callback_(std::move(add_callback)),
      ...
      isolate_name_server_(std::move(isolate_name_server)) {
  AddOrRemoveTaskObserver(true /* add */);
}

void UIDartState::AddOrRemoveTaskObserver(bool add) {
  auto task_runner = task_runners_.GetUITaskRunner();
  if(! task_runner) {// This may happen in case the isolate has no thread affinity (for example,
    // the service isolate).
    return;
  }
  FML_DCHECK(add_callback_ && remove_callback_);
  if (add) {
    // Here is a lambda expression passed to add_callback_ as a function
    add_callback_(reinterpret_cast<intptr_t> (this),this] () {this->FlushMicrotasksNow(); });// Perform all microtasks
  } else {
    remove_callback_(reinterpret_cast<intptr_t> (this)); }}Copy the code

The focus here is on add_callback_, which is assigned when the UIDartState object is initialized. Since DartIsolate inherits from UIDartState, look at the creation of the DartIsolate object.

[-> flutter/runtime/dart_isolate.cc]

DartIsolate::DartIsolate(const Settings& settings,
                         TaskRunners task_runners,
                         fml::WeakPtr<SnapshotDelegate> snapshot_delegate,
                         fml::WeakPtr<IOManager> io_manager,
                         fml::RefPtr<SkiaUnrefQueue> unref_queue,
                         fml::WeakPtr<ImageDecoder> image_decoder,
                         std: :string advisory_script_uri,
                         std: :string advisory_script_entrypoint,
                         bool is_root_isolate)
    : UIDartState(std::move(task_runners),
                  // The value of add_callback_
                  settings.task_observer_add,
                  settings.task_observer_remove,
                  std::move(snapshot_delegate),
                  std::move(io_manager),
                  std::move(unref_queue),
                  std::move(image_decoder),
                  advisory_script_uri,
                  advisory_script_entrypoint,
                  settings.log_tag,
                  settings.unhandled_exception_callback,
                  DartVMRef::GetIsolateNameServer()),
      is_root_isolate_(is_root_isolate) {
  phase_ = Phase::Uninitialized;
}
Copy the code

In the code above, assign the task_observer_add of the Settings object to add_callback_. Settings are created and initialized in the Init function of FlutterMain, so when FlutterMain is initialized, task_observer_add is assigned to the Settings object.

[-> flutter/shell/platform/android/flutter_main.cc]

void FlutterMain::Init(JNIEnv* env,
                       jclass clazz,
                       jobject context,
                       jobjectArray jargs,
                       jstring kernelPath,
                       jstring appStoragePath,
                       jstring engineCachesPath) {
  std: :vector<std: :string> args;
  args.push_back("flutter");
  for (auto& arg : fml::jni::StringArrayToVector(env, jargs)) {
    args.push_back(std::move(arg));
  }
  auto command_line = fml::CommandLineFromIterators(args.begin(), args.end());

  autosettings = SettingsFromCommandLine(command_line); .//add_callback_ corresponds to the function
  settings.task_observer_add = [](intptr_t key, fml::closure callback) {
    fml::MessageLoop::GetCurrent().AddTaskObserver(key, std::move(callback));
  };

  settings.task_observer_remove = [](intptr_tkey) { fml::MessageLoop::GetCurrent().RemoveTaskObserver(key); }; . }Copy the code

The MessageLoopImpl object that the thread corresponds to, MessageLoopAndroid inherits from MessageLoopImpl. So look at the implementation of the AddTaskObserver function.

[-> flutter/fml/message_loop_impl.cc]

void MessageLoopImpl::AddTaskObserver(intptr_t key,
                                      const fml::closure& callback) {
  if(callback ! =nullptr) {
    // Each MessageLoopImpl object has a unique queue_id_
    task_queue_->AddTaskObserver(queue_id_, key, callback);
  } else {...}
}
Copy the code

The task_queue_ is a MessageLoopTaskQueues object whose AddTaskObserver function is implemented as follows.

[-> flutter/fml/message_loop_task_queues.cc]

void MessageLoopTaskQueues::AddTaskObserver(TaskQueueId queue_id, intptr_t key, const fml::closure& callback) { std::scoped_lock queue_lock(GetMutex(queue_id)); / / UIDartState for the key. The callback containing the FlushMicrotasksNow call is value queue_entries_[queue_id]->task_observers[key] = STD ::move(callback); }Copy the code

Queue_entries_ in this code is a map with TaskQueueId as the key and TaskQueueEntry object as value, and task_observers in TaskQueueEntry is also a map. So the AddTaskObserver function stores the callback containing the FlushMicrotasksNow call into the map with the UIDartState object as the key.

If the OnMessage function of DartMessageHandler is used to process event Handler messages and normal messages in UI_ISOLATE, the code is as follows.

[->third_party/tonic/dart_message_handler.cc]

void DartMessageHandler::Initialize(TaskDispatcher dispatcher) {
  // Only can be called once.TONIC_CHECK(! task_dispatcher_ && dispatcher); task_dispatcher_ = dispatcher; Dart_SetMessageNotifyCallback(MessageNotifyCallback); }void DartMessageHandler::OnMessage(DartState* dart_state) {
  auto task_dispatcher_ = dart_state->message_handler().task_dispatcher_;

  auto weak_dart_state = dart_state->GetWeakPtr();
  // In Android, tasks are handed over to loops in the UI thread.
  // In iOS, this is also done through a loop-like message handler
  task_dispatcher_([weak_dart_state]() {
    if (autodart_state = weak_dart_state.lock()) { dart_state->message_handler().OnHandleMessage(dart_state.get()); }}); }Copy the code

The task_dispatcher_ in this code is set when the Initialize function is called from the DartMessageHandler object. The Initialize function is called when RootIsolate is initialized, according to the startup flow of the Flutter Engine.

[-> flutter/runtime/dart_isolate.cc]

bool DartIsolate::InitializeIsolate(
    std: :shared_ptr<DartIsolate> embedder_isolate,
    Dart_Isolate isolate,
    char** error) {
  ...
  // Set the message handler for the UI threadSetMessageHandlingTaskRunner(GetTaskRunners().GetUITaskRunner()); .return true;
}

void DartIsolate::SetMessageHandlingTaskRunner(
    fml::RefPtr<fml::TaskRunner> runner) {
  if(! IsRootIsolate() || ! runner) {return;
  }

  message_handling_task_runner_ = runner;
  
  // Set the message handler
  message_handler().Initialize(
      [runner](std::function<void()> task) { runner->PostTask(task); });
}
Copy the code

Task_dispatcher_ adds tasks to Looper via PostTask.

[-> flutter/fml/task_runner.cc]

TaskRunner::TaskRunner(fml::RefPtr<MessageLoopImpl> loop)
    : loop_(std::move(loop)) {}
void TaskRunner::PostTask(const fml::closure& task) {
  loop_->PostTask(task, fml::TimePoint::Now());
}
Copy the code

Take the Android platform for example, where loop_ is a MessageLoopAndroid object. So let’s look at the implementation of PostTask in MessageLoopAndroid.

[-> flutter/fml/message_loop_impl.cc]


void MessageLoopImpl::PostTask(const fml::closure& task,
                               fml::TimePoint target_time) {
  ...
  task_queue_->RegisterTask(queue_id_, task, target_time);
}
Copy the code

The PostTask function ultimately calls the RegisterTask function of task_queue_.

[-> flutter/fml/message_loop_task_queues.cc]

void MessageLoopTaskQueues::RegisterTask(TaskQueueId queue_id,
                                         const fml::closure& task,
                                         fml::TimePoint target_time) {
  std::scoped_lock queue_lock(GetMutex(queue_id));

  size_t order = order_++;
  const auto& queue_entry = queue_entries_[queue_id];
  queue_entry->delayed_tasks.push({order, task, target_time});
  TaskQueueId loop_to_wake = queue_id;
  if (queue_entry->subsumed_by != _kUnmerged) {
    loop_to_wake = queue_entry->subsumed_by;
  }
  WakeUpUnlocked(loop_to_wake,
                 queue_entry->delayed_tasks.top().GetTargetTime());
}

void MessageLoopTaskQueues::WakeUpUnlocked(TaskQueueId queue_id,
                                           fml::TimePoint time) const {
  if (queue_entries_.at(queue_id)->wakeable) {
    queue_entries_.at(queue_id)->wakeable->WakeUp(time);
  }
}
Copy the code

In the RegisterTask function, add the task task to the priority queue delayed_tasks. The WakeUp function of the MessageLoopAndroid object is then called.

[-> flutter/fml/platform/android/message_loop_android.cc]

void MessageLoopAndroid::WakeUp(fml::TimePoint time_point) {
  bool result = TimerRearm(timer_fd_.get(), time_point);
  FML_DCHECK(result);
}
Copy the code

The WakeUp function uses the TimerRearm function to WakeUp looper at the right time. Based on the previous UI thread creation, the OnEventFired function that executed the MessageLoopAndroid object is in the read_event_fd callback after Looper is woken up. In this function, the FlushTasks function of MessageLoopAndroid object is directly called. The following is the implementation of FlushTasks function.

[-> flutter/fml/message_loop_impl.cc]


void MessageLoopImpl::FlushTasks(FlushType type) {
  TRACE_EVENT0("fml"."MessageLoop::FlushTasks");
  std: :vector<fml::closure> invocations;

  task_queue_->GetTasksToRunNow(queue_id_, type, invocations);

  for (const auto& invocation : invocations) {
    // Execute the normal callback method
    invocation();
    std: :vector<fml::closure> observers =
        task_queue_->GetObserversToNotify(queue_id_);
    for (const auto& observer : observers) {
      The observer corresponds to the FlushMicrotasksNow function of the UIDartState object, where all microtasks are performedobserver(); }}}void MessageLoopImpl::RunExpiredTasksNow() {
  FlushTasks(FlushType::kAll);
}
Copy the code

In FlushTasks function, all microtasks will be performed after the completion of each expired Event Handler task or asynchronous task.

At this point, in UI_ISOLATE, microtasks are finally performed in one of two ways.

  1. In the callwindowthe_beginFramewith_drawFrameAll microtasks are processed between methods. You can’t do time-consuming operations in microtasks that would affect UI rendering.
  2. In each of the expiredevent handlerAfter a task or asynchronous task completes, all microtasks are executed.

Let’s look at the timing of microtasks in non-UI_ISOLATE. Also mainly divided into the following situations.

  1. According to theAnalysis of the Timer principle of FlutterA piece can be learned in everyevent handlerAfter a task or asynchronous task completes, all microtasks are executed.
  2. If this is a production environment,IsolateThe message type iskDrainServiceExtensionsMsgAnd the message priority iskImmediateAction, all microtasks will also be performed

4, summarize

So that’s how microtasks work and how they work. There are still some difficulties. By analyzing the principle of the Flutter Timer, we can basically understand the message mechanism of the Flutter, so that we can know better when using microtasks and other asynchronous tasks.

【 References 】

Gain insight into the Flutter messaging mechanism