This article is the first signed article of the rare earth nuggets technology community. Reprinting is prohibited within 14 days, and reprinting is prohibited without authorization after 14 days. Infringement must be investigated!
Zhang Fengjie Telie - Produced
In the last article, by studying Future
the realized that a Future
class is just a layer of encapsulation of a 监听-通知
mechanism . And introduced Timer
and scheduleMicrotask
two ways to trigger the callback function asynchronously. In this article, we will continue to explore these two and understand the hidden 消息处理机制
and 微任务循环
.
1. Understanding message processing from Timer
We already know, Future.delayed
and the delay created by the Future
constructor 异步任务
, is essentially the Timer
object setting timed callback. For callbacks, there are two very important clues:
Where did the callback function go?
How is the callback function triggered?
1. How the Timer callback is triggered
Timer
For , the Duration
callback function can be triggered by specifying the duration. Note that here is Duration
not strict duration, nor is it due to instability in the program's execution 误差
. In the following case, after 200 ms
the , loopAdd
the synchronous task executed by , is 10 亿次
accumulated , which is time-consuming 444 ms
.
Even if the expected delay execution time of the asynchronous setting is 200 ms
, the actual runtime 异步回调
must be triggered after the synchronous task has finished executing. Timer
The following callbacks 449 ms
are fired after . A friend made a metronome before. By Timer
opening the loop task, I found that the error is very large, because Timer
it is not used to deal with precise time, and it needs to be used in scenes that require high time accuracy 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;
}
复制代码
This is also easy to understand. After the loopAdd
method is pushed into the stack, it will only pop out of the stack after execution. Timer
The callback of , even if 200 ms
it completes at , can only be processed when the synchronization task is finished. This is a little different from daily life, when the water is boiling during sweeping, you can deal with the hot water first, and then go back to sweep the floor. What is the mechanism of asynchronous task callback in Dart? The following uses the callback as a clue to track.
The best way to understand how callbacks are fired is 断点调试
. As follows, break the point in the Timer
callback function, and the running result is as follows:
You can clearly see that 回调函数
is _RawReceivePortImpl._handleMessage
triggered by the method. That is to say, Dart 虚拟机
there is a message 消息
processing the callback event is triggered.
2. Get to know_RawReceivePortImpl#_handleMessage
_RawReceivePortImpl
The class is in the isolate_patch.dart
file and _handleMessage
is a private method. From the debugging point of view tag2 处
, the handler
function of is Timer
the trigger that causes the callback function passed in in to fire. So let's analyze this object:
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
The priority of callback processing is relatively high. If there are many things processed in the microtask queue, the execution of other events may be delayed. Therefore, computationally intensive time-consuming tasks should not be scheduleMicrotask
processed . If a certain piece of logic does not want to affect the execution of the following code, but also wants to be prioritized over asynchronous tasks, it can be scheduleMicrotask
thrown into the microtask queue by using .
Here, we have a Timer
preliminary understanding of the mechanism of Dart message processing, and scheduleMicrotask
how microtasks are queued and executed in a loop. The process can be simplified into the following figure, which is what we often say 事件循环 Event Loop
. In fact, this graph is not very accurate, but it can basically represent the event model.
In addition, what is introduced here is the category of Dart
knowledge , which, for the Flutter
framework, Dart
is the knowledge base of language features in . After the main
function is executed, the application program is always running, which means that there must be a 循环机制 Loop
guarantee that program will not exit. In addition, the rendering mechanism, gesture events, animation mechanism and Dart
the message processing mechanism and microtask cycle of , together constitute Flutter
the 事件循环机制
.
That's it for this article. With the foundation of these basic knowledge, in the next article, we will go into Stream
it and take a look at its internal implementation principle. And explore the related knowledge of Stream
stream transformation.