[Flutter Asynchronous Programming-Lu] | Explore o processamento de mensagens do Dart e o loop de microtarefas

Este artigo é o primeiro artigo assinado da comunidade de tecnologia de pepitas de terras raras. A reimpressão é proibida dentro de 14 dias e a reimpressão é proibida sem autorização após 14 dias. A violação deve ser investigada!
Zhang Fengjiete - Produzido

No último artigo, estudando Futureo percebemos que uma Futureclasse é apenas uma camada de encapsulamento de um 监听-通知mecanismo . E introduzido Timere scheduleMicrotaskduas maneiras de acionar a função de retorno de chamada de forma assíncrona. Neste artigo, continuaremos a explorar esses dois e entender o oculto 消息处理机制e 微任务循环.


1. Entendendo o processamento de mensagens do Timer

Já sabemos, Future.delayede o atraso criado pelo Futureconstrutor 异步任务, é essencialmente o retorno de chamada temporizado de configuração do Timerobjeto . Para retornos de chamada, há duas pistas muito importantes:

Para onde foi a função de retorno de chamada?
Como a função de retorno de chamada é acionada?


1. Como o retorno de chamada do Timer é acionado

TimerPara , a Durationfunção de retorno de chamada pode ser acionada especificando a duração. Observe que aqui Durationnão duração estrita, nem é devido à instabilidade na execução do programa 误差. No caso a seguir, após 200 msa , loopAdda tarefa síncrona executada por , é 10 亿次acumulada , o que é demorado 444 ms.

Mesmo que o tempo de execução de atraso esperado da configuração assíncrona seja 200 ms, o tempo de execução real 异步回调deve ser acionado após a conclusão da execução da tarefa síncrona. TimerOs seguintes retornos de chamada 449 mssão disparados após . Um amigo fez um metrônomo antes, e ao Timerativar a tarefa de loop, descobriu que o erro é muito grande, pois Timernão é usado para lidar com tempo preciso, e precisa ser usado em cenas que exigem alta precisão de tempo Ticker.

imagem.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;
}
复制代码

Isso também é fácil de entender.Depois que o loopAddmétodo é inserido na pilha, ele só sairá da pilha após a execução. TimerO retorno de chamada de , mesmo que 200 msseja concluído em , só poderá ser processado quando a tarefa de sincronização for concluída. Isso é um pouco diferente da vida cotidiana, quando a água está fervendo durante a varredura, você pode lidar com a água quente primeiro e depois voltar para varrer o chão. Qual é o mecanismo de retorno de chamada de tarefa assíncrona no Dart? O seguinte usa o retorno de chamada como uma pista para rastrear.


A melhor maneira de entender como os retornos de chamada são acionados é 断点调试. Da seguinte forma, quebre o ponto na função de Timerretorno e o resultado da execução é o seguinte:

imagem.png

Você pode ver claramente que 回调函数é _RawReceivePortImpl._handleMessageacionado pelo método. Ou seja, Dart 虚拟机existe um mecanismo de 消息processamento o evento de retorno de chamada é acionado.


2. Conheça_RawReceivePortImpl#_handleMessage

_RawReceivePortImplA classe está no isolate_patch.dartarquivo e _handleMessageé um método privado. Do ponto de vista da depuração tag2 处, a handlerfunção de é Timero gatilho que faz com que a função de retorno de chamada passada seja acionada. Então vamos analisar 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 方法。

imagem.png

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

imagem.png

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


3. _RawReceivePortImpl 设置处理器

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

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

imagem.png


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

imagem.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 类型成员。

imagem.png


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

class _Timer implements Timer {
复制代码

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

imagem.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();
复制代码

imagem.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 对象。

imagem.png


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

imagem.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】 中触发:

imagem.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 静态方法发送消息。

imagem.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 优先级的,也就是普通消息。

imagem.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

scheduleMicrotaskA prioridade do processamento de retorno de chamada é relativamente alta.Se houver muitas coisas processadas na fila de microtarefas, a execução de outros eventos pode ser atrasada. Portanto, tarefas que consomem muito tempo computacionalmente não devem ser scheduleMicrotaskprocessadas . Se uma certa parte da lógica não quiser afetar a execução do código a seguir, mas também quiser ser priorizada em tarefas assíncronas, ela pode ser scheduleMicrotasklançada na microtarefa fila usando .


Aqui, temos uma compreensão Timerpreliminar do mecanismo de processamento de mensagens do Dart e scheduleMicrotaskcomo as microtarefas são enfileiradas e executadas em um loop. O processo pode ser simplificado na figura a seguir, que é o que costumamos dizer 事件循环 Event Loop. Na verdade, esse gráfico não é muito preciso, mas pode representar basicamente o modelo de evento.

image.png

Além disso, o que é introduzido aqui é a categoria de Dartconhecimento , que, para o Flutterframework, Darté a base de conhecimento de recursos da linguagem em . Após a mainexecução da função, o programa aplicativo está sempre em execução, o que significa que deve haver uma 循环机制 Loopgarantia de que programa não será encerrado. Além disso, o mecanismo de renderização, os eventos de gestos, o mecanismo de animação e Darto mecanismo de processamento de mensagens e o ciclo de microtarefa do , juntos, constituem Fluttero 事件循环机制.

Por este artigo é isso.Com a base desses conhecimentos básicos, no próximo artigo, vamos entrar Streamnele e dar uma olhada em seu princípio de implementação interna. E explore o conhecimento relacionado à transformação de Streamfluxo .

Acho que você gosta

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