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 Future
el dimos cuenta de que una Future
clase es solo una capa de encapsulación de un 监听-通知
mecanismo . Timer
Y presentó scheduleMicrotask
dos 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.delayed
y el retraso creado por el Future
constructor 异步任务
, es esencialmente la devolución de llamada temporizada de configuración del Timer
objeto . 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
Timer
Para , la Duration
función de devolución de llamada se puede activar especificando la duración. Nótese que aquí Duration
no duración estricta, ni se debe a inestabilidad en la ejecución del programa 误差
. En el siguiente caso, después 200 ms
de 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. Timer
Las siguientes devoluciones de llamada 449 ms
se activan después de . Un amigo hizo un metrónomo antes y, al Timer
activar la tarea de bucle, descubrió que el error es muy grande, porque Timer
no se usa para manejar el tiempo preciso y debe usarse en escenas que requieren una precisión de tiempo alta Ticker
.
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 loopAdd
método se inserta en la pila, solo saldrá de la pila después de la ejecución. Timer
La devolución de llamada de , incluso si 200 ms
se 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 Timer
devolución y el resultado de la ejecución es el siguiente:
Puede ver claramente que 回调函数
se _RawReceivePortImpl._handleMessage
activa 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
_RawReceivePortImpl
La clase está en el isolate_patch.dart
archivo y _handleMessage
es un método privado. Desde el punto de vista de la depuración tag2 处
, la handler
función de es Timer
el 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
处被赋值,它的值为: 以 id
为 key
在 _portMap
中取值的 handler
属性。也就是说,_portMap
是一个映射对象,键是 int
类型,值是 Map<String, dynamic>
类型。
static final _portMap = <int, Map<String, dynamic>>{};
复制代码
通过调试可以发现,此时的 handler
对象类型如下:是只有一个参数,无返回值的 _handleMessage
静态方法。那这个 _handleMessage
是什么呢?反正肯定不是上面的 _handleMessage
方法。
从调试中可以清楚地看到,这个 handler
函数触发时,会进入 _Timer._handleMessage
方法中,所以 handler
自然指的是 _Timer._handleMessage
,函数签名也是可以对得上的。
这就有个问题:既然 _RawReceivePortImpl#_handleMessage
方法中,可以通过 _portMap
找到该函数,那么_Timer#_handleMessage
一定在某个时刻被加入到了 _portMap
中。下面,我们通过这条线索,追踪一下 _RawReceivePortImpl
如何设置处理器。
3. _RawReceivePortImpl
设置处理器
在 _Timer
中搜索一下 _handleMessage
就很容易看到它被设置的场景:如下所示,在 _Timer#_createTimerHandler
方法中, 458 行
创建 _RawReceivePortImpl
类型的 port
对象,并在 459 行
为其设置 handler
,值正是 _Timer#_handleMessage
方法。
其实从这里就能大概猜到 _RawReceivePortImpl#handler
的 set
方法的处理逻辑,大家可以暂停脑补一下,猜猜看。
没错,_RawReceivePortImpl#handler
的 set
方法,就是为 _portMap
映射添加元素的。这就和 _RawReceivePortImpl#_handleMessage
从 _portMap
映射中取出 handler
函数对象交相呼应。
到这里,可以知道 _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.current
是 Zone.root
, 其对应的实现类是 _RootZone
。也就是说 Timer
的构造会触发 _RootZone#createTimer
方法。
static const Zone root = _rootZone;
const _Zone _rootZone = const _RootZone();
复制代码
所以,callback
流浪的第一站是 _RootZone
的 createTimer
方法,如下所示。其中会触发 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
类型成员。
如下是内部文件 timer_impl.dart 中的代码。其中 _Timer
是 Timer
的实现类,也是 dart
中 Timer
构造时创建的实际类型。从中可以看到 VMLibraryHooks.timerFactory
被赋值为 _Timer._factory
,所以上面说的 factory
是什么就不言而喻了。
class _Timer implements Timer {
复制代码
从 481 行
代码可以看出,callback
进入的第三站是 _Timer
的构造函数:
在 _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();
复制代码
另外说一句,上面的 tag1
处 timer._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();
复制代码
另外,_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
对象。
在 _runTimers
中,会遍历 pendingTimers
,取出 Timer
中的 _callback
成员, 这个成员就算创建 Timer
时的入参函数,在 398
行触发回调。
这就是 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
中的 sendPort
的 get
方法,由 _get_sendport
外部方法实现。最终由 C++
代码实现,在 【dart-lang/sdk/runtime/lib/isolate.cc】 中触发:
---->[_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. 零延迟定时器消息的发送
_SendPortImpl
是 SendPort
的唯一实现类。零延迟定时器加入链表队列后,发送 _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_
入口的逻辑。会通过 PortMap
的 PostMessage
静态方法发送消息。
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.cc
对 PostMessage
方法实现时,由 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
值将消息加入到消息队列中。 IsOOB
由 Message
对象的优先级 Priority
枚举属性决定。 为 OOB 消息
是优先处理的消息。
从 SendPortImpl_sendInternal_
中加入的消息是 kNormalPriority
优先级的,也就是普通消息。
---->[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
:
在【runtime/vm/object_store.cc】 的 LazyInitIsolateMembers
中,会将 _RawReceivePortImpl
的 _handleMessage
存储起来。这就是 C++
中 DartLibraryCalls::HandleMessage
会触发Dart
中 _RawReceivePortImpl#_handleMessage
的原因。
到这里,我们再回首一下本文开始时的调试结果。大家可以结合下面的线索自己疏通一下: Timer
对象的创建、 Dart
端设置监听、回调函数的传递、 Timer
队列的维护 、Timer
发送和接收端口的创建、Timer
发送消息、消息加入 C++ 中消息息队列、最后 C++
处理消息,通知 Dart
端触发 _RawReceivePortImpl#_handleMessage
。
这就是 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
处理:
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
,从而完成定时回调的任务。
可以看出一个小小的 延迟任务
, 其中涉及的知识也是非常广泛的。通过 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
所示的函数。 tag2
的 entryPoint()
方法就是 main
方法。
2. scheduleMicrotask 回调函数的触发
如下所示,在 scheduleMicrotask
的回调中打上断点,可以看出它也是由 _RawReceivePortImpl#_handleMessage
触发的。难道 scheduleMicrotask
也是走的消息处理机制? 这个问题萦绕我很久,现在幡然醒悟:其实不然 !
void main() {
scheduleMicrotask(() {
print("executed1"); //<-- 断点
});
}
复制代码
认清这里的 _RawReceivePortImpl#_handleMessage
是谁触发的,对理解 scheduleMicrotask
至关重要。从下面两个调试中 _handleMessage
的 id
可以看出,这是同一个 _RawReceivePortImpl#_handleMessage
:
main 函数触发
scheduleMicrotask 回调函数触发
如下所示,在 192
行触发 main 函数
,当函数体执行完毕,会通过 _runPendingImmediateCallback
执行微任务回调。所以才会产生 scheduleMicrotask
回调函数后于 main
函数体执行的异步效果。
3. 微任务循环的执行
_runPendingImmediateCallback
方法会执行 _pendingImmediateCallback
函数对象。从调试来看,运行时该函数对象为 _startMicrotaskLoop
:
@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
的本质原因。
现在回到上一篇中,就很容易理解永远不会被回调的 零延迟 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
方法。
另外,记得在 Flutter 1.12
之前, scheduleWarmUpFrame
是通过 scheduleMicrotask
来执行 handleBeginFrame
和 handleDrawFrame
的,之后都改成 Timer.run
的 零延迟回调
了。
scheduleMicrotask
La 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 scheduleMicrotask
procesarse .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 scheduleMicrotask
en la microtarea hacer cola usando .
Aquí, tenemos una comprensión Timer
preliminar del mecanismo de procesamiento de mensajes de Dart y scheduleMicrotask
có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.
Además, lo que se introduce aquí es la categoría de Dart
conocimiento , que, para el Flutter
marco, Dart
es la base de conocimiento de las características del lenguaje en . Después de main
ejecutar la función, el programa de aplicación siempre se está ejecutando, lo que significa que debe haber una 循环机制 Loop
garantía de que programa no se cerrará. Además, el mecanismo de representación, los eventos de gestos, el mecanismo de animación y Dart
el mecanismo de procesamiento de mensajes y el ciclo de microtareas de , juntos constituyen Flutter
el 事件循环机制
.
Eso es todo por este artículo. Con la base de estos conocimientos básicos, en el próximo artículo, lo analizaremos Stream
y veremos su principio de implementación interna. Y explore el conocimiento relacionado con la transformación de Stream
corrientes .