[Programación asincrónica de Flutter-Lu] | Explore el procesamiento de mensajes de Dart y el ciclo de microtareas

Este artículo es el primer artículo firmado de la comunidad tecnológica de pepitas de tierras raras. Está prohibida la reimpresión dentro de los 14 días y la reimpresión sin autorización después de los 14 días. ¡Se debe investigar la infracción!
Zhang Fengjiete - Producido

En el último artículo, al estudiar Futureel dimos cuenta de que una Futureclase es solo una capa de encapsulación de un 监听-通知mecanismo . TimerY presentó scheduleMicrotaskdos formas de activar la función de devolución de llamada de forma asíncrona. En este artículo, continuaremos explorando estos dos y comprenderemos lo oculto 消息处理机制y 微任务循环.


1. Comprender el procesamiento de mensajes de Timer

Ya lo sabemos, Future.delayedy el retraso creado por el Futureconstructor 异步任务, es esencialmente la devolución de llamada temporizada de configuración del Timerobjeto . Para las devoluciones de llamada, hay dos pistas muy importantes:

¿Adónde fue la función de devolución de llamada?
¿Cómo se activa la función de devolución de llamada?


1. Cómo se activa la devolución de llamada del temporizador

TimerPara , la Durationfunción de devolución de llamada se puede activar especificando la duración. Nótese que aquí Durationno duración estricta, ni se debe a inestabilidad en la ejecución del programa 误差. En el siguiente caso, después 200 msde la loopAdd, se 10 亿次acumula que lleva mucho tiempo 444 ms.

Incluso si el tiempo de ejecución de retraso esperado de la configuración asíncrona es 200 ms, el tiempo de ejecución real 异步回调debe activarse después de que la tarea síncrona haya terminado de ejecutarse. TimerLas siguientes devoluciones de llamada 449 msse activan después de . Un amigo hizo un metrónomo antes y, al Timeractivar la tarea de bucle, descubrió que el error es muy grande, porque Timerno se usa para manejar el tiempo preciso y debe usarse en escenas que requieren una precisión de tiempo alta Ticker.

imagen.png

main() {
  int start = DateTime.now().millisecondsSinceEpoch;
  Timer(const Duration(milliseconds: 200),(){
    int end = DateTime.now().millisecondsSinceEpoch;
    print('Timer Cost: ${(end-start)} ms');
  });
  loopAdd(1000000000);
}

int loopAdd(int count) {
  int startTime = DateTime.now().millisecondsSinceEpoch;
  int sum = 0;
  for (int i = 0; i <= count; i++) {
    sum+=i;
  }
  int cost = DateTime.now().millisecondsSinceEpoch-startTime;
  print('loopAdd Cost: $cost ms');
  return sum;
}
复制代码

Esto también es fácil de entender.Después de que el loopAddmétodo se inserta en la pila, solo saldrá de la pila después de la ejecución. TimerLa devolución de llamada de , incluso si 200 msse completa en , solo se puede procesar cuando finaliza la tarea de sincronización. Esto es un poco diferente de la vida diaria, cuando el agua está hirviendo durante el barrido, primero puede lidiar con el agua caliente y luego volver a barrer el piso. ¿Cuál es el mecanismo de devolución de llamada de tareas asincrónicas en Dart? Lo siguiente usa la devolución de llamada como una pista para rastrear.


La mejor manera de comprender cómo se activan las devoluciones de llamada es 断点调试. De la siguiente manera, rompa el punto en la función de Timerdevolución y el resultado de la ejecución es el siguiente:

imagen.png

Puede ver claramente que 回调函数se _RawReceivePortImpl._handleMessageactiva por el método. Es decir, Dart 虚拟机hay un mecanismo de 消息procesamiento se activa el evento de devolución de llamada.


2. Conozca_RawReceivePortImpl#_handleMessage

_RawReceivePortImplLa clase está en el isolate_patch.dartarchivo y _handleMessagees un método privado. Desde el punto de vista de la depuración tag2 处, la handlerfunción de es Timerel activador que hace que se active la función de devolución de llamada. Así que analicemos este objeto:

class _RawReceivePortImpl implements RawReceivePort {

---->[_RawReceivePortImpl#_handleMessage]----
@pragma("vm:entry-point", "call")
static _handleMessage(int id, var message) {
  final handler = _portMap[id]?['handler']; // tag1
  if (handler == null) {
    return null;
  }
  handler(message); // tag2
  _runPendingImmediateCallback();
  return handler;
}
复制代码

handler 函数对象在 tag1 处被赋值,它的值为: 以 idkey_portMap 中取值的 handler 属性。也就是说,_portMap 是一个映射对象,键是 int 类型,值是 Map<String, dynamic> 类型。

static final _portMap = <int, Map<String, dynamic>>{};
复制代码

通过调试可以发现,此时的 handler 对象类型如下:是只有一个参数,无返回值的 _handleMessage 静态方法。那这个 _handleMessage 是什么呢?反正肯定不是上面的 _handleMessage 方法。

imagen.png

从调试中可以清楚地看到,这个 handler 函数触发时,会进入 _Timer._handleMessage 方法中,所以 handler 自然指的是 _Timer._handleMessage ,函数签名也是可以对得上的。

imagen.png

这就有个问题:既然 _RawReceivePortImpl#_handleMessage 方法中,可以通过 _portMap 找到该函数,那么_Timer#_handleMessage 一定在某个时刻被加入到了 _portMap 中。下面,我们通过这条线索,追踪一下 _RawReceivePortImpl 如何设置处理器。


3. _RawReceivePortImpl 设置处理器

_Timer 中搜索一下 _handleMessage 就很容易看到它被设置的场景:如下所示,在 _Timer#_createTimerHandler 方法中, 458 行 创建 _RawReceivePortImpl 类型的 port 对象,并在 459 行 为其设置 handler,值正是 _Timer#_handleMessage 方法。

其实从这里就能大概猜到 _RawReceivePortImpl#handlerset 方法的处理逻辑,大家可以暂停脑补一下,猜猜看。

imagen.png


没错,_RawReceivePortImpl#handlerset 方法,就是为 _portMap 映射添加元素的。这就和 _RawReceivePortImpl#_handleMessage_portMap 映射中取出 handler 函数对象交相呼应。

imagen.png

到这里,可以知道 _Timer#_createTimerHandler 方法会将回调加入映射中去,就可以大胆的猜测一下,在 Timer 对象创建后,一定会触发 _createTimerHandler


4. Timer 对象的创建与回调函数的 "流浪"

首先 Timer 是一个 抽象类 ,本身并不能实例化对象。如下是 Timer 的普通工厂构造,返回值是由 Zone 对象通过 createTimer 方法创建的对象,也就是说该方法一定会返回 Timer 的实现类对象。另外,我们有了一条新线索,可以追寻一下入参中的回调 callback 是如何在源码中 "流浪" 的。

---->[Timer]----
factory Timer(Duration duration, void Function() callback) {
  if (Zone.current == Zone.root) {
    return Zone.current.createTimer(duration, callback);
  }
  return Zone.current
      .createTimer(duration, Zone.current.bindCallbackGuarded(callback));
}
复制代码

由于默认情况下, Zone.currentZone.root , 其对应的实现类是 _RootZone 。也就是说 Timer 的构造会触发 _RootZone#createTimer 方法。

static const Zone root = _rootZone;
const _Zone _rootZone = const _RootZone();
复制代码

所以,callback流浪的第一站是 _RootZonecreateTimer 方法,如下所示。其中会触发 Timer_createTimer 静态方法,这也是 callback 流浪的第二站:

---->[_RootZone#createTimer]----
Timer createTimer(Duration duration, void f()) {
  return Timer._createTimer(duration, f);
}
复制代码

Timer#_createTimer 静态方法是一个 external 的方法,在 Flutter 提供的 sdk 中是找不到的。

---->[Timer#_createTimer]----
external static Timer _createTimer(
    Duration duration, void Function() callback);
复制代码

可以在 dart-lang/sdk/sdk/_internal/vm/lib 中找到内部文件,比如这里是 timer_patch.dart。在调试时可以显示相关的代码,但无法进行断点调试。 (PS 不知道有没有人知道如何能调试这种内部文件)

接下来, callback 会进入第三站:作为 factory 函数的第二参回调的处理逻辑。现在问题来了,factory 对象是什么东西? 从代码来看,它是 VMLibraryHooks 类的静态成员 timerFactory。由于 factory 可以像函数一样通过 () 执行,毫无疑问它是一个函数对象,函数的签名也很明确: 三个入参,类型依次是 int回调函数bool ,且返回 Timer 类型成员。

imagen.png


如下是内部文件 timer_impl.dart 中的代码。其中 _TimerTimer 的实现类,也是 dartTimer 构造时创建的实际类型。从中可以看到 VMLibraryHooks.timerFactory 被赋值为 _Timer._factory ,所以上面说的 factory 是什么就不言而喻了。

class _Timer implements Timer {
复制代码

481 行 代码可以看出,callback 进入的第三站是 _Timer 的构造函数:

imagen.png

_Timer 的构造函数中,callback 进入的第四站: _Timer#_createTimer 方法。其中 _Timer 通过 _internal 构造,将 callback 作为参数传入,为 _Timer_callback 成员赋值。自此, callback 进入第五站,被 _Timer 持有,结束了流浪生涯,终于安家。

---->[_Timer 构造]----
factory _Timer(int milliSeconds, void callback(Timer timer)) {
  return _createTimer(callback, milliSeconds, false);
}

---->[_Timer#_createTimer]----
static _Timer _createTimer(
  if (milliSeconds < 0) {
    milliSeconds = 0;
  }
  int now = VMLibraryHooks.timerMillisecondClock();
  int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds);
  _Timer timer =  new _Timer._internal(callback, wakeupTime, milliSeconds, repeating);
  timer._enqueue(); // tag1
  return timer;
}

---->[_Timer#_internal]----
_Timer._internal(
    this._callback, this._wakeupTime, this._milliSeconds, this._repeating)
    : _id = _nextId();
复制代码

另外说一句,上面的 tag1timer._enqueue() 会触发 _notifyZeroHandler ,从而导致 _createTimerHandler 的触发。也就是上面 第 3 小节 中为 _RawReceivePortImpl 设置处理器的契机。

_enqueue
   --> _notifyZeroHandler
      --> _createTimerHandler
复制代码

到这里可以把线索串连一下,想一想:一个 Timer 对象是如何实例化的;其中 callback 对象是如何一步步流浪,最终被 _Timer 对象持有的; _createTimerHandler 是如何为 _RawReceivePortImpl 设置 handler 的。


5. Timer 中的堆队列与链表队列

_Timer#_enqueue 中,当定义的时长非零时,通过堆结构维护 Timer 队列。 数据结构定义在 _TimerHeap 中,就算一个非常简单的 二叉堆,感兴趣的可以自己看看。 _Timer 类持有 _TimerHeap 类型的静态成员 _heap, 这里 enqueue 就是 进入队列 的意思。

---->[_Timer#_heap]----
static final _heap = new _TimerHeap();
复制代码

imagen.png


另外,_Timer 本身是一个链表结构,其中持有 _indexOrNext 属性用于指向下一节点,并维护 _firstZeroTimer_lastZeroTimer 链表首尾节点。在 _enqueue 方法中,当 Timer 时长为 0 ,会通过链表结构维护 Timer 队列。

---->[_Timer]----
Object? _indexOrNext;
static _Timer _lastZeroTimer = _sentinelTimer;
static _Timer? _firstZeroTimer;

---->[_Timer#_enqueue]----
if (_milliSeconds == 0) {
  if (_firstZeroTimer == null) {
    _lastZeroTimer = this;
    _firstZeroTimer = this;
  } else {
    _lastZeroTimer._indexOrNext = this; //
    _lastZeroTimer = this;
  }
  // Every zero timer gets its own event.
  _notifyZeroHandler();
复制代码

_Timer#_handleMessage 中,会触发 _runTimers 方法,其中入参的 pendingTimers 就是从链表 队列中获取的,已超时或零时 Timer 对象。

imagen.png


_runTimers 中,会遍历 pendingTimers,取出 Timer中的 _callback 成员, 这个成员就算创建 Timer 时的入参函数,在 398 行触发回调。

imagen.png

这就是 Timer 回调函数在 Dart 中的一生。把这些理清楚之后, Timer 对象在 Dart 中的处理流程就算是比较全面了。


二、消息的发送端口和接收端口

其实 Dart 中的 Timer 代码只是流程中的一部分。对于消息处理机制来说,还涉及 Dart 虚拟机C++ 代码的处理。也就是 Isolate 的通信机制,这里稍微介绍一下,也有利于后面对 Isolate 的认识。


1. Timer 的发送端口 SendPort

Timer 创建并进入队列后,会在 _createTimerHandler 中创建 _RawReceivePortImpl 对象,这个对象从名称上来看,是一个 接收端口 的实现类。顾名思义,接收端是由于接收消息、处理消息的。通过前面的调试我们知道,接收端消息的处理方法是由 handler 进行设置,如下 tag1 处。

既然有 接收端口,自然有 发送端口_Timer 中持有 SendPort 类型的 _sendPort 对象。该成员在 tag2 处由 接收端口sendPort 赋值。

---->[_Timer#_sendPort]----
static SendPort? _sendPort;
static _RawReceivePortImpl? _receivePort;

static void _createTimerHandler() {
  var receivePort = _receivePort;
  if (receivePort == null) {
    assert(_sendPort == null);
    final port = _RawReceivePortImpl('Timer');
    port.handler = _handleMessage; // tag1
    _sendPort = port.sendPort; // tag2 
    _receivePort = port;
    _scheduledWakeupTime = 0;
  } else {
    receivePort._setActive(true);
  }
  _receivePortActive = true;
}
复制代码

_RawReceivePortImpl 中的 sendPortget 方法,由 _get_sendport 外部方法实现。最终由 C++ 代码实现,在 【dart-lang/sdk/runtime/lib/isolate.cc】 中触发:

imagen.png

---->[_RawReceivePortImpl#sendPort]----
SendPort get sendPort {
  return _get_sendport();
}

@pragma("vm:external-name", "RawReceivePortImpl_get_sendport")
external SendPort _get_sendport();
复制代码

_createTimerHandler 方法触发之后, _Timer 中的发送、接收端口已经准备完毕。之后自然是要发送消息。在 _Timer#_enqueue 中如果是时长为 0 的定时器,在 tag2 处会通过 _sendPort 发送 _ZERO_EVENT 的事件。

---->[_Timer#_enqueue]----
void _enqueue() {
  if (_milliSeconds == 0) {
    // 略...
    _notifyZeroHandler(); // tag1
    // 略...
}

---->[_Timer#_notifyZeroHandler]----
static void _notifyZeroHandler() {
  if (!_receivePortActive) {
    _createTimerHandler();
  }
  _sendPort!.send(_ZERO_EVENT); // tag2
}
复制代码

2. 零延迟定时器消息的发送

_SendPortImplSendPort 的唯一实现类。零延迟定时器加入链表队列后,发送 _ZERO_EVENT 消息使用的是如下的 send 方法,最终会通过 SendPortImpl_sendInternal_C++ 方法进行实现,向 Dart 虚拟机 发送消息。

class _SendPortImpl implements SendPort {

@pragma("vm:entry-point", "call")
void send(var message) {
  _sendInternal(message);
}

@pragma("vm:external-name", "SendPortImpl_sendInternal_")
external void _sendInternal(var message);
复制代码

如下是 【dart-lang/sdk/runtime/lib/isolate.cc】SendPortImpl_sendInternal_ 入口的逻辑。会通过 PortMapPostMessage 静态方法发送消息。

imagen.png


PortMap 是定义在 port.h 在的 C++ 类,其中 PostMessage 是一个静态方法,从注释来看,该方法会将消息加入消息队列:

---->[sdk\runtime\vm\port.h]----
class PortMap : public AllStatic {

  // Enqueues the message in the port with id. Returns false if the port is not
  // active any longer.
  //
  // Claims ownership of 'message'.
  static bool PostMessage(std::unique_ptr<Message> message,
                        bool before_events = false);
}         
复制代码

port.ccPostMessage 方法实现时,由 MessageHandler#PostMessage 进行处理:

---->[sdk\runtime\vm\port.cc]----
bool PortMap::PostMessage(std::unique_ptr<Message> message,
                          bool before_events) {
  MutexLocker ml(mutex_);
  if (ports_ == nullptr) {
    return false;
  }
  auto it = ports_->TryLookup(message->dest_port());
  if (it == ports_->end()) {
    // Ownership of external data remains with the poster.
    message->DropFinalizers();
    return false;
  }
  MessageHandler* handler = (*it).handler;
  ASSERT(handler != nullptr);
  handler->PostMessage(std::move(message), before_events);
  return true;
}
复制代码

MessageHandler 类中有两个 MessageQueue 消息队列成员, queue_oob_queue_ 。如下,在 message_handler.cc 中,会根据 message#IsOOB 值将消息加入到消息队列中。 IsOOBMessage 对象的优先级 Priority 枚举属性决定。 为 OOB 消息 是优先处理的消息。

SendPortImpl_sendInternal_ 中加入的消息是 kNormalPriority 优先级的,也就是普通消息。

imagen.png

---->[MessageHandler]----
MessageQueue* queue_;
MessageQueue* oob_queue_;

---->[sdk\runtime\vm\message_handler.cc]----
void MessageHandler::PostMessage(std::unique_ptr<Message> message,
                                 bool before_events) {
  Message::Priority saved_priority;
    // 略...
    saved_priority = message->priority();
    if (message->IsOOB()) {
      oob_queue_->Enqueue(std::move(message), before_events);
    } else {
      queue_->Enqueue(std::move(message), before_events);
    }
   // 略...
  }

  // Invoke any custom message notification.
  MessageNotify(saved_priority);
}
复制代码

3. Dart 中 _RawReceivePortImpl#_handleMessage 的触发

在消息加入队列之后,会触发 MessageNotify 进行处理,这里就不细追了。最后看一下,C++ 的代码是如何导致 Dart 中的_RawReceivePortImpl#_handleMessage 方法触发的。如下所示,【runtime/vm/dart_entry.cc】 中定义了很多入口函数。其中 DartLibraryCalls::HandleMessage 里会触发 object_store 中存储的 handle_message_function :

image.png


【runtime/vm/object_store.cc】LazyInitIsolateMembers 中,会将 _RawReceivePortImpl_handleMessage 存储起来。这就是 C++DartLibraryCalls::HandleMessage 会触发Dart_RawReceivePortImpl#_handleMessage 的原因。

image.png


到这里,我们再回首一下本文开始时的调试结果。大家可以结合下面的线索自己疏通一下: Timer 对象的创建、 Dart 端设置监听、回调函数的传递、 Timer 队列的维护 、Timer 发送和接收端口的创建、Timer 发送消息、消息加入 C++ 中消息息队列、最后 C++ 处理消息,通知 Dart 端触发 _RawReceivePortImpl#_handleMessage

image.png

这就是 Timer 异步任务最简单的消息处理流程。


3. 有延迟定时器消息的发送

前面介绍的是零延迟的消息发送,下面看一下有延迟时消息发送的处理。如下所示,当进入队列时 _milliSeconds 非零,加入堆队列,通过 _notifyEventHandler 来发送消息:

---->[_Timer#_enqueue]----
void _enqueue() {
  if (_milliSeconds == 0) {
    // 略...
  } else {
    _heap.add(this);
    if (_heap.isFirst(this)) {
      _notifyEventHandler();
    }
  }
}
复制代码

_notifyEventHandler 在开始会进行一些空链表的校验,触发 _scheduleWakeup 方法,告知 EventHandler 在指定的时间后告知当前的 isolate 。其中发送通知的核心方法是 VMLibraryHooks#eventHandlerSendData

---->[_Timer#_enqueue]----
static void _notifyEventHandler() {
  // 略 基础判断...
  if ((_scheduledWakeupTime == 0) || (wakeupTime != _scheduledWakeupTime)) {
    _scheduleWakeup(wakeupTime);
  }
}

---->[_Timer#_enqueue]----
// Tell the event handler to wake this isolate at a specific time.
static void _scheduleWakeup(int wakeupTime) {
  if (!_receivePortActive) {
    _createTimerHandler();
  }
  VMLibraryHooks.eventHandlerSendData(null, _sendPort!, wakeupTime);
  _scheduledWakeupTime = wakeupTime;
}
复制代码

通过 eventHandler 发送的消息,由 C++【sdk/runtime/bin/eventhandler.cc】 处理,如下所示:交由 EventHandler 类触发 SendData 处理:

image.png

EventHandler 中持有 EventHandlerImplementation 类型的 delegate_ 成员, SendData 方法最终由实现类完成:

class EventHandler {
 public:
  EventHandler() {}
  void SendData(intptr_t id, Dart_Port dart_port, int64_t data) {
    delegate_.SendData(id, dart_port, data);
  }
 // 略...
 private:
  friend class EventHandlerImplementation;
  EventHandlerImplementation delegate_;
复制代码

不同的平台都有相关的实现类,具体处理逻辑就不细追了。因为定时的延迟任务不会阻塞当前线程,使用肯定要交给 C++ 创建子启线程处理。延迟完成之后,最终会通知 Dart 端触发 _RawReceivePortImpl#_handleMessage,从而完成定时回调的任务。

image.png

可以看出一个小小的 延迟任务 , 其中涉及的知识也是非常广泛的。通过 Timer 来了解消息处理机制是一个比较好的切入点。


三、 main 函数的启动与微任务循环

上一篇我们知道 scheduleMicrotask 的回调对于 main 函数体来说也是异步执行的,但那它和 Timer 触发的延迟任务有着本质的区别。接下来我们将对 scheduleMicrotask 进行全面分析,从中就能明白为什么 scheduleMicrotask 中的回调总可以在 Timer 之前触发。 不过在此之前,必须说明一下 main 函数的触发。


1. main 函数的触发

在我们的认知中,main 函数是程序的入口。但如果打个断点调试一下会发现,其实 main 函数本身是一个回调。和 Timer 回调一样,也是由 _RawReceivePortImpl#_handleMessage 方法触发的:


所以 main 函数的触发也是涉及 消息通知机制,如下所示: _startIsolate 触发 _delayEntrypointInvocation 方法,其中创建 RawReceivePort 接收端看对象,并为 handler 赋值。

@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 = (_) { // tag1
    port.close();
    if (allowZeroOneOrTwoArgs) {
      // 略...
      } else {
        entryPoint(); // tag2
      }
    } else {
      entryPoint(message);
    }
  };
  port.sendPort.send(null);
}
复制代码

也就是说收到消息,触发 _RawReceivePortImpl#_handleMessage 时,执行的 handler 就是 tag1 所示的函数。 tag2entryPoint() 方法就是 main 方法。

image.png


2. scheduleMicrotask 回调函数的触发

如下所示,在 scheduleMicrotask 的回调中打上断点,可以看出它也是由 _RawReceivePortImpl#_handleMessage 触发的。难道 scheduleMicrotask 也是走的消息处理机制? 这个问题萦绕我很久,现在幡然醒悟:其实不然 !

image.png

void main() {
  scheduleMicrotask(() {
    print("executed1"); //<-- 断点
  });
}
复制代码

认清这里的 _RawReceivePortImpl#_handleMessage 是谁触发的,对理解 scheduleMicrotask 至关重要。从下面两个调试中 _handleMessageid 可以看出,这是同一个 _RawReceivePortImpl#_handleMessage

  • main 函数触发

image.png

  • scheduleMicrotask 回调函数触发

image.png


如下所示,在 192 行触发 main 函数 ,当函数体执行完毕,会通过 _runPendingImmediateCallback 执行微任务回调。所以才会产生 scheduleMicrotask 回调函数后于 main 函数体执行的异步效果。

image.png


3. 微任务循环的执行

_runPendingImmediateCallback 方法会执行 _pendingImmediateCallback 函数对象。从调试来看,运行时该函数对象为 _startMicrotaskLoop:

image.png

@pragma("vm:entry-point", "call")
void _runPendingImmediateCallback() {
  final callback = _pendingImmediateCallback;
  if (callback != null) {
    _pendingImmediateCallback = null;
    callback();
  }
}
复制代码

_startMicrotaskLoop 是定义在 schedule_microtask.dart 中的私有方法,其中会触发 _microtaskLoop 方法,执行微任务队列:

void _startMicrotaskLoop() {
  _isInCallbackLoop = true;
  try {
    _microtaskLoop(); // tag1 
  } finally {
    _lastPriorityCallback = null;
    _isInCallbackLoop = false;
    if (_nextCallback != null) {
      _AsyncRun._scheduleImmediate(_startMicrotaskLoop);
    }
  }
}
复制代码

微任务通过 _AsyncCallbackEntry 进行维护,其中持有 callback 回调和 next 节点,是一个链表结构。_nextCallback_lastCallback 全局变量为首尾节点:

typedef void _AsyncCallback();

class _AsyncCallbackEntry {
  final _AsyncCallback callback;
  _AsyncCallbackEntry? next;
  _AsyncCallbackEntry(this.callback);
}

/// Head of single linked list of pending callbacks.
_AsyncCallbackEntry? _nextCallback;

/// Tail of single linked list of pending callbacks.
_AsyncCallbackEntry? _lastCallback;
复制代码

_microtaskLoop 中,会遍历 _nextCallback 链表,触发节点中的回调函数, 如 tag1 所示。


/// Head of single linked list of pending callbacks.
_AsyncCallbackEntry? _nextCallback;

/// Tail of single linked list of pending callbacks.
_AsyncCallbackEntry? _lastCallback;


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)(); // tag1
  }
}
复制代码

4. 微任务队列事件的进入

当使用 scheduleMicrotask 方法时,会触发 _rootScheduleMicrotask 方法,在 _scheduleAsyncCallback 中为微任务列表添加元素:

@pragma('vm:entry-point', 'call')
void scheduleMicrotask(void Function() callback) {
  _Zone currentZone = Zone._current;
  if (identical(_rootZone, currentZone)) {
    _rootScheduleMicrotask(null, null, _rootZone, callback);
    return;
  }
}

void _rootScheduleMicrotask(  Zone? self, ZoneDelegate? parent, Zone zone, void f()) {
  // 略...
  _scheduleAsyncCallback(f);
}
复制代码

具体逻辑如下:通过 callback 创建 _AsyncCallbackEntry 对象,并加入到链表队列中。注意一点,当微任务加入到队列中时,此时不在执行微任务循环,会通过 _AsyncRun._scheduleImmediate_startMicrotaskLoop 函数安排在事件队列之前。

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;
  }
}

class _AsyncRun {
  /// Schedule the given callback before any other event in the event-loop.
  external static void _scheduleImmediate(void Function() callback);
}
复制代码

5. 为什么微任务的处理的优先级较高

总的来看,微任务的处理是比较简单的,它和 Timer 有着本质的区别,并不会使用 发送/接收端口,也不通过 C++ 的消息机制来处理任务。所以微任务的处理更加小巧,灵活。由于 main 函数执行完后,接下来会通过微任务循环处理 main 中的微任务列表,这就是 微任务 先于 零延迟 Timer 的本质原因。

image.png

现在回到上一篇中,就很容易理解永远不会被回调的 零延迟 Timer 。 由于 scheduleMicrotask 不断添加微任务,所以 _microtaskLoop 会一直执行,无法出栈。

main() {
  Timer.run(() { print("executed"); });  // Will never be executed.
  foo() {
    scheduleMicrotask(foo);  // Schedules [foo] in front of other events.
  }
  foo();
}
复制代码

其实微任务的并不是很常用,在 Flutter 框架中的使用场景也并不多。我印象中比较深的是在 手势竞技场 中,当只有一个竞技者时,通过 scheduleMicrotask, 在回调在异步触发 _resolveByDefault 方法。

image.png

另外,记得在 Flutter 1.12 之前, scheduleWarmUpFrame 是通过 scheduleMicrotask 来执行 handleBeginFramehandleDrawFrame 的,之后都改成 Timer.run零延迟回调 了。

image.png

scheduleMicrotaskLa prioridad del procesamiento de devolución de llamada es relativamente alta.Si hay muchas cosas procesadas en la cola de microtareas, la ejecución de otros eventos puede retrasarse. Por lo tanto, las tareas que consumen mucho tiempo desde el punto de vista computacional no deben scheduleMicrotaskprocesarse .Si una cierta parte de la lógica no quiere afectar la ejecución del siguiente código, pero también quiere tener prioridad sobre las tareas asincrónicas, puede incluirse scheduleMicrotasken la microtarea hacer cola usando .


Aquí, tenemos una comprensión Timerpreliminar del mecanismo de procesamiento de mensajes de Dart y scheduleMicrotaskcómo las microtareas se ponen en cola y se ejecutan en un bucle. El proceso se puede simplificar en la siguiente figura, que es lo que solemos decir 事件循环 Event Loop. De hecho, este gráfico no es muy preciso, pero básicamente puede representar el modelo de eventos.

image.png

Además, lo que se introduce aquí es la categoría de Dartconocimiento , que, para el Fluttermarco, Dartes la base de conocimiento de las características del lenguaje en . Después de mainejecutar la función, el programa de aplicación siempre se está ejecutando, lo que significa que debe haber una 循环机制 Loopgarantía de que programa no se cerrará. Además, el mecanismo de representación, los eventos de gestos, el mecanismo de animación y Dartel mecanismo de procesamiento de mensajes y el ciclo de microtareas de , juntos constituyen Flutterel 事件循环机制.

Eso es todo por este artículo. Con la base de estos conocimientos básicos, en el próximo artículo, lo analizaremos Streamy veremos su principio de implementación interna. Y explore el conocimiento relacionado con la transformación de Streamcorrientes .

Supongo que te gusta

Origin juejin.im/post/7155270458353385486
Recomendado
Clasificación