上周六一次視頻面中,我在回答問題中提到一句,flutter 的同級Future 都是順序執(zhí)行的,面試官立刻問我,你確定嗎,一下子把我問住了,這個結(jié)論是我忘記在哪里看到的 blog 上面提到的,當(dāng)時就一直記在了心里,我沒有去驗(yàn)證過,也沒有需要使用的場景,所以只是當(dāng)一個結(jié)論記著,自己也說不出個所以然來,所以被人問住也是很正常的.很羞愧的是,上周之前,我并不知道 flutter 面試需要考些什么,甚至自以為考官會問我應(yīng)用層的問題,真是面的一塌糊涂.
話說回來,本著技術(shù)探討的初心,我決定自己測試并探究下這,同級Future 到底是有序還是無序的.
代碼測試
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_psd/common/utils/psdllog.dart';
class AysncTestPage extends StatelessWidget {
const AysncTestPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'異步測試',
style: TextStyle(),
),
),
body: GestureDetector(onTap: () {
// testMultipleAsync();
testAsync();
}),
);
}
void testAsync() {
// 第一種
Future(() => psdllog(1),);
Future(() => psdllog(2));
Future(() => psdllog(3));
Future(() => psdllog(4));
Future(() => psdllog(5));
Future(() => psdllog(6));
Future(() => psdllog(7));
Future(() => psdllog(8));
Future(() => psdllog(9));
Future(() => psdllog(10));
Future(() => psdllog(11));
Future(() => psdllog(12));
// 第二種
// Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(11));
// Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(22));
// Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(33));
// Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(44));
// Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(55));
// Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(66));
// Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(77));
// Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(88));
// Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(99));
// 第三種
// for (var i = 0; i < 10000; i++) {
// Future.delayed(Duration(milliseconds: 1500)).then((value) => psdllog(i));
// }
// 第四種
// Future(() => psdllog(111111),);
// Future.delayed(Duration(milliseconds: 500), () {
// psdllog(2222222);
// });
// Future(() => psdllog(333333),);
}
testMultipleAsync() {
// 測試各類 async
print("main start");
final future = Future(() => null);
Future(() => print("task1"));
Future(() => print("task2")).then((_) {
print("task3");
scheduleMicrotask(() => print('task4'));
}).then((_) => print("task5"));
future.then((_) => print("task6"));
scheduleMicrotask(() => print('task7'));
Future.value(3).then((val) => print('task11'));
Future(() => print('task8'))
.then((_) => Future(() => print('task9')))
.then((_) => print('task10'));
print("main end");
}
}
代碼中,我測試的邏輯很簡單,在點(diǎn)擊手勢中調(diào)用testAsync方法,然后多次測試我展示的前三種方法,結(jié)果均是先加入的先執(zhí)行,后加入的后執(zhí)行,這與我們的iOS 中的串行隊(duì)列類似.
源碼查找
Future實(shí)現(xiàn)
可以看出來,future 的調(diào)用是在 Timer 中執(zhí)行的,
Timer.run
實(shí)際上是創(chuàng)建了一個 duration 為 zero 的 timer 去run
static void run(void Function() callback) {
new Timer(Duration.zero, callback);
}
現(xiàn)在問題就轉(zhuǎn)換成了,多個Timer的 duration為 zero 的時候,調(diào)用順序是什么
Timer 源碼
factory Timer(Duration duration, void Function() callback) {
if (Zone.current == Zone.root) {
// No need to bind the callback. We know that the root's timer will
// be invoked in the root zone.
return Zone.current.createTimer(duration, callback);
}
return Zone.current
.createTimer(duration, Zone.current.bindCallbackGuarded(callback));
}
timer 的創(chuàng)建是在調(diào)用 zone 的createTimer生成的.我們最終可以追溯到的是
Timer createTimer(Duration duration, void f()) {
var implementation = this._createTimer;
ZoneDelegate parentDelegate = implementation.zone._parentDelegate;
CreateTimerHandler handler = implementation.function;
return handler(implementation.zone, parentDelegate, this, duration, f);
}
其實(shí)到這里,還是無法看出實(shí)際 timer 的執(zhí)行步驟
整個dart:async 中 timer 的代碼在上面展示了,不知道是什么原因,真正的_timer 實(shí)現(xiàn)我并沒有找到,Zone 函數(shù)中關(guān)于 timer 的創(chuàng)建都被拋給 delegate(rootZone)了,到目前為止,我們只能知道_createTimer的執(zhí)行是按順序執(zhí)行下來的,代碼到這里為止,同級的Future 是按順序_createTimer 的,但是 實(shí)際上 timer 的創(chuàng)建,即Timer._createTimer,我并沒有找到,線索到這就斷掉了...
timer 的真正實(shí)現(xiàn)
終于,在多番查找后,我終于在網(wǎng)上找到了 dart 對于 timer 的源碼實(shí)現(xiàn), 戳這
這篇文章講的很細(xì)很細(xì),作者大逗大人對于每個代碼都增加了注釋,建議每個人認(rèn)真反復(fù)閱讀三遍,需要了解人可以全部看看.
回到正題,我們本著我們探究問題去查找,我只截取對我們問題有幫助的部分代碼.
- 創(chuàng)建 timer 的實(shí)現(xiàn)
static Timer _createTimer(
void callback(Timer timer), int milliSeconds, bool repeating) {
//milliSeconds不能小于0再愈,小于0也就意味著超時贩绕,需要立即執(zhí)行登夫。
if (milliSeconds < 0) {
milliSeconds = 0;
}
//獲取當(dāng)前時間
int now = VMLibraryHooks.timerMillisecondClock();
//得到Timer的喚醒時間
int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds);
//創(chuàng)建一個Timer對象
_Timer timer =
new _Timer._internal(callback, wakeupTime, milliSeconds, repeating);
//將新創(chuàng)建的Timer放到適當(dāng)?shù)慕Y(jié)構(gòu)中寞肖,并在必要時進(jìn)行對應(yīng)的通知矢洲。
//如果Timer中是異步任務(wù)僧免,則加入到鏈表中七兜,否則加入到二叉堆中
timer._enqueue();
return timer;
}
注意這里, 將新創(chuàng)建的Timer放到適當(dāng)?shù)慕Y(jié)構(gòu)中孽糖,并在必要時進(jìn)行對應(yīng)的通知玉工。如果Timer中是非異步任務(wù)(即 zero_event, 時間戳為0的任務(wù))羽资,則加入到鏈表中,否則加入到二叉堆中,但是不管他們是鏈表形式插入還是二叉堆形式插入,他們的存取都是有順序的.
- timer的排序
//將Timer添加到二叉堆或者鏈表中遵班,如果喚醒時間相同則按照先進(jìn)先出的規(guī)則來取出
void _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();
} else {
_heap.add(this);
if (_heap.isFirst(this)) {
_notifyEventHandler();
}
}
}
可以推斷出,在我執(zhí)行之前的代碼中
Future(() => psdllog(1),);
Future(() => psdllog(2));
Future(() => psdllog(3));
Future(() => psdllog(4));
Future(() => psdllog(5));
Future(() => psdllog(6));
1到9的 Future 代碼會按照從上而下的順序被添加到鏈表中
Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(11));
Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(22));
Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(33));
Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(44));
Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(55));
Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(66));
Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(77));
Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(88));
Future.delayed(Duration(milliseconds: 500)).then((value) => psdllog(99));
11-99會被添加到二叉堆中,我們不用管二叉堆內(nèi)部是如何實(shí)現(xiàn)的,只需要知道這個
//首先根據(jù)喚醒時間來排序屠升,如果喚醒時間相同則根據(jù)timer的_id來排序
int _compareTo(_Timer other) {
int c = _wakeupTime - other._wakeupTime;
if (c != 0) return c;
return _id - other._id;
}
對于時間相同的 timer,判斷 timer 的 _id,那_timer 的 id 是如何被賦值的呢? 在上面的_createTimer其實(shí)已經(jīng)提到過了
//創(chuàng)建一個Timer對象
_Timer._internal(
this._callback, this._wakeupTime, this._milliSeconds, this._repeating)
: _id = _nextId();
//獲取下一個可用id
static int _nextId() {
var result = _idCount;
_idCount = (_idCount + 1) & _ID_MASK;
return result;
}
看到這里其實(shí)就能發(fā)現(xiàn),對于Delay 時間相同的異步任務(wù),timer 的 id 是按自動遞增1的,即先執(zhí)行 Timer.run 的 timer id 更小點(diǎn),這也能推斷出延時任務(wù)的 Future,duration 相同的話,先運(yùn)行的Future 先執(zhí)行.
- 調(diào)用 timer
static void _handleMessage(msg) {
var pendingTimers;
if (msg == _ZERO_EVENT) {
//獲取包含異步任務(wù)的timer
pendingTimers = _queueFromZeroEvent();
} else {
_scheduledWakeupTime = null; // Consumed the last scheduled wakeup now.
//獲取已經(jīng)超時的timer
pendingTimers = _queueFromTimeoutEvent();
}
//執(zhí)行Timer的回調(diào)方法
_runTimers(pendingTimers);
//如果當(dāng)前沒有待執(zhí)行的Timer,則通知event handler或者關(guān)閉port
_notifyEventHandler();
}
跳轉(zhuǎn)到_queueFromZeroEvent()
static List _queueFromZeroEvent() {
var pendingTimers = new List();
//從二叉堆中查詢到期時間小于_firstZeroTimer的timer狭郑,并加入到一個List中
var timer;
while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) {
timer = _heap.removeFirst();
pendingTimers.add(timer);
}
//獲取鏈表中的第一個timer
timer = _firstZeroTimer;
_firstZeroTimer = timer._indexOrNext;
timer._indexOrNext = null;
pendingTimers.add(timer);
return pendingTimers;
}
這里可以看出,調(diào)用時取的是_firstZeroTimer,在該方法中腹暖,會將二叉堆中喚醒時間比鏈表中的第一個timer
對象喚醒時間還短的timer
對象加入到集合pendingTimers
中,然后再將鏈表中的第一個timer
對象加入到集合pendingTimers
中翰萨。
取非延時的 timer 是從 鏈表的第一個一個個往后取的.
取異步任務(wù)的 timer是從二叉樹 先按時間升序,然后再按_id 升序
所以,Duration 相同的前提下,Future 的同級代碼一定會按照順序執(zhí)行timer 中的function
拖展
答案是1,-500,2,3,500,其實(shí)-500的 future 其實(shí)也是被添加到異步任務(wù)中,只不過,在執(zhí)行過程中,
_notifyEventHandler-> _handleMessage-> _queueFromTimeoutEvent(獲取超時的 timer),執(zhí)行.
iOS 的思考
future 同級按順序執(zhí)行,與 iOS 串行隊(duì)列很像,都是先進(jìn)先出,按順序執(zhí)行,只不過 iOS 的不開源,導(dǎo)致我做了這么久,也沒去思考過,iOS 串行隊(duì)列的實(shí)現(xiàn)到底是怎么實(shí)現(xiàn)的.dart 開源的好處也就是體現(xiàn)在了這里,
一個小問題
在研究這個的同時脏答,我也順便看了下其他的關(guān)于異步任務(wù),微隊(duì)列的相關(guān)知識,也算是真正的了解了 flutter 的 future,scheduleMicrotask等執(zhí)行順序,那么,現(xiàn)在壓力來到了讀者這邊,下面這道題是我在網(wǎng)上看到的一道比較全面的題目了
test() {
// 測試各類 async
print("main start");
final future = Future(() => null);
Future(() => print("task1"));
Future(() => print("task2")).then((_) {
print("task3");
scheduleMicrotask(() => print('task4'));
}).then((_) => print("task5"));
future.then((_) => print("task6"));
scheduleMicrotask(() => print('task7'));
Future.value(3).then((val) => print('task11'));
Future(() => print('task8'))
.then((_) => Future(() => print('task9')))
.then((_) => print('task10'));
print("main end");
}
還有下面這道是我想出來的題目,跟上面類似,不過增加 Future.microtask,俗話說,兩道題全對,那才叫真的理解了,希望讀者們可以講對,并且講明為什么.
test() {
psdllog(0);
var future1 = Future(() => psdllog(1));
var future2 = Future.value(0).then((value) => psdllog(2));
Future.value(0).then((value) => psdllog(3));
Future.microtask(() => psdllog(4));
scheduleMicrotask(() => psdllog(5));
future1.then((value) {
psdllog(6);
scheduleMicrotask(() {
psdllog(7);
Future(() => psdllog(8),);
});
});
future2.then((value) => psdllog(9));
Future(() => psdllog(10),);
psdllog(11);
}
這些題目才算是真正的面試八股文,匯聚了 部分 future會被放到微隊(duì)列,微隊(duì)列與事件隊(duì)列的執(zhí)行循環(huán),future 的 then 函數(shù)調(diào)用順序,微隊(duì)列任務(wù)或事件任務(wù)中調(diào)用 future 等執(zhí)行順序,你們能真正弄懂這個考題嗎,后面我會找時間把這部分內(nèi)容補(bǔ)上的,唉,坑越埋越多了~
最近一段時間,先把面試八股文背好先,找個穩(wěn)妥點(diǎn)的工作,如果要更新文章的話,估計(jì)都會跟面試有搭噶,其他的坑后面再說~