上一篇我們提到了ReactNative的《javaScript promise await 宏任務(wù) 微任務(wù)》的內(nèi)容净蚤,如果想看的話請點擊 《javaScript promise await 宏任務(wù) 微任務(wù)》心傀,在這一篇的時候我們就提到了要寫一篇關(guān)于Flutter的future await事件隊列, 微任務(wù)相關(guān)內(nèi)容的文章症副,并且兩門語言要來做一個對比启泣,其實關(guān)于這個話題網(wǎng)絡(luò)上已經(jīng)有一些文章寫得還不錯了决摧,我的這篇文章主要想寫一些不一樣的東西亿蒸,把一些網(wǎng)絡(luò)錯誤的東西給矯正,把一些沒有提及的東西給他細(xì)化明確掌桩,也給自己做一個自我的總結(jié)边锁,如果你還迷惑在這幾個概念中的話,如果你還被他們的執(zhí)行順序搞得痛苦不堪的話波岛,如果你想痛定思痛搞清楚的話茅坛,OK那么你來對地方了,那么我們今天就弄清楚他們幾個的原理则拷,那我們現(xiàn)在就開始吧:
首先來看一下Dart的事件隊列(Event loop):
Dart的事件循和JavaScript的基本一樣贡蓖。循環(huán)中有兩個隊列。一個是微任務(wù)隊列(MicroTask queue)煌茬,一個是事件隊列(Event queue)斥铺。其中Dart的事件隊列對應(yīng)的是JavaScript的宏隊列,Dart的微隊列對應(yīng)的是和JavaScript的微隊列
- 事件隊列包含外部事件坛善,例如I/O, Timer晾蜘,繪制事件,F(xiàn)uture(部分情況)等等眠屎。
- 微任務(wù)隊列則包含有Dart內(nèi)部的微任務(wù)剔交,主要是通過scheduleMicrotask,F(xiàn)uture(部分情況)來調(diào)度改衩。
再來看一張很熟悉的圖片:
Dart的事件循環(huán)的運行遵循以下規(guī)則:
- 首先處理所有微任務(wù)隊列里的微任務(wù)岖常。
- 處理完所有微任務(wù)以后。從事件隊列里取1個事件進行處理葫督。
- 回到微任務(wù)隊列繼續(xù)循環(huán)竭鞍。
注意第一步里的所有,也就是說在處理事件隊列之前候衍,Dart要先把所有的微任務(wù)處理完笼蛛。如果某一時刻微任務(wù)隊列里有8個微任務(wù)洒放,事件隊列有2個事件蛉鹿,Dart也會先把這8個微任務(wù)全部處理完再從事件隊列中取出1個事件處理,之后又會回到微任務(wù)隊列去看有沒有未執(zhí)行的微任務(wù)往湿。(這一點與JavaScript很像妖异,JavaScript也是處理下一個宏任務(wù)的時候回把當(dāng)前的微任務(wù)全部都清空處理)
總而言之惋戏,就是對微任務(wù)隊列是一次性全部處理,對于事件隊列是一次只處理一個他膳。
那么在Dart中如何讓你的代碼異步執(zhí)行呢响逢?很簡單,把要異步執(zhí)行的代碼放在微任務(wù)隊列或者事件隊列里就行了棕孙。
可以調(diào)用scheduleMicrotask來讓代碼以微任務(wù)的方式異步執(zhí)行
scheduleMicrotask((){
print('a microtask');
});
可以調(diào)用Timer.run來讓代碼以Event loop的方式異步執(zhí)行
Timer.run((){
print('a event loop’);
});
上面已經(jīng)簡單的介紹了事件隊列與微任務(wù)舔亭,現(xiàn)在我們介紹下Future,也就是對應(yīng)JavaScript之中的Promise
Future提供了一系列構(gòu)造函數(shù)供你選擇蟀俊。:
- 創(chuàng)建一個立刻在事件隊列里運行的Future:
Future(() => print('立刻在Event queue中運行的Future'));
- 創(chuàng)建一個延時1秒在事件隊列里運行的Future:
Future.delayed(const Duration(seconds:1), () => print('1秒后在Event queue中運行的Future'));
- 創(chuàng)建一個在微任務(wù)隊列里運行的Future:
Future.microtask(() => print('在Microtask queue里運行的Future'));
- 創(chuàng)建一個同步運行的Future:
Future.sync(() => print('同步運行的Future'));
上面式Future的幾種常見的創(chuàng)建方式钦铺,我們使用過JavaScript就知道創(chuàng)建了Promise是需要使用then來作為回調(diào)函數(shù)的處理的,Dart的Future也是一樣的用 then來處理回調(diào)和鏈?zhǔn)秸{(diào)用肢预,Dart的then函數(shù)也會返回一個Future作為鏈?zhǔn)秸{(diào)用的條件矛洞,這一點與JavaScript的Promise的then極為的相似,例如:
Future(()=> print('task0’))
.then((_)=> print('callback0’))
.then((_)=> print('callback1’));
以上就是對Future的一些簡單用法的介紹烫映,F(xiàn)uture背后的運行機制還是有一些復(fù)雜的沼本,如果搞不懂連代碼的執(zhí)行順序都摸不清楚,這里先列幾個來自Dart官網(wǎng)的關(guān)于Future的說明:
-
你通過then串起來的那些回調(diào)函數(shù)在Future完成的時候會被立即執(zhí)行锭沟,也就是說它們是同步執(zhí)行抽兆,而不是被調(diào)度異步執(zhí)行。
(這種運行情況最普通最常見的then運行方式族淮,但是不包括下面的提到的那種情況)這點對比JavaScript的then是完全不同的郊丛,JavaScript里面Promise的then是被調(diào)度到微隊列執(zhí)行的,不會馬上同步執(zhí)行
-
如果Future在調(diào)用then串起回調(diào)函數(shù)之前已經(jīng)完成瞧筛,那么這些回調(diào)函數(shù)會被調(diào)度到微任務(wù)隊列異步執(zhí)行厉熟。
這種情況非常特殊也很常見,這種情況下的then在Dart中不是立馬執(zhí)行的较幌,例如通過Future.value()實例化的Future會被調(diào)度到微任務(wù)隊列異步完成揍瑟,這種情況就非常類似JavaScript的執(zhí)行 Promise.resolve(val).then(val => console.log(val)) 的情況,這種情況下JavaScript也是通過resolve立即返回一個fullfilled狀態(tài)的Promise乍炉,而then也是在微隊列執(zhí)行的
-
通過Future()和Future.delayed()實例化的Future不會同步執(zhí)行绢片,它們會被調(diào)度到事件隊列異步執(zhí)行。
這種情況依然非常常見岛琼,通過構(gòu)造函數(shù)實例化的Future會被調(diào)度到 事件隊列異步執(zhí)行 底循,這種情況與JavaScript也是完全不同的,JavaScript的Promise的構(gòu)造函數(shù)是在宏任務(wù)可以立即執(zhí)行的(說白了是同步的)槐瑞,例如:
//JavaScript
new Promise(function(resolve) {
console.log('promise');
resolve(true)
}).then(function() {
console.log('then');
})
// promise
// then
- 通過Future.sync()實例化的Future會同步執(zhí)行其入?yún)⒑瘮?shù)熙涤,然后(除非這個入?yún)⒑瘮?shù)返回一個Future)調(diào)度到微任務(wù)隊列來完成自己,類似于第2條。
從上述說明可以得出結(jié)論祠挫,F(xiàn)uture中的代碼至少會有一部分被異步調(diào)度執(zhí)行的那槽,要么是其入?yún)⒑瘮?shù)和回調(diào)被異步調(diào)度執(zhí)行,要么就只有回調(diào)被異步調(diào)度執(zhí)行等舔,但是不會兩部分都異步執(zhí)行
下面我們來個簡單的例子感受下:
import "dart:async";
main(List<String> args) {
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");
}
//main start
//main end
//task7
//task11
//task6
//task1
//task2
//task3
//task5
//task4
//task8
//task9
//task10
打印順序簡單的總結(jié)下:
main函數(shù)先執(zhí)行骚灸,所以main start和main end先執(zhí)行,沒有任何問題慌植;
main函數(shù)執(zhí)行過程中甚牲,會將一些任務(wù)分別加入到EventQueue和MicrotaskQueue中;
task7通過scheduleMicrotask函數(shù)調(diào)用蝶柿,所以它被最早加入到MicrotaskQueue鳖藕,會被先執(zhí)行,task11通過Future.value()創(chuàng)建也是會加入到MicrotaskQueue只锭,接著執(zhí)行
然后開始執(zhí)行EventQueue著恩,task6的 final future = Future(() => null) 最早創(chuàng)建,我們知道future構(gòu)造函數(shù)創(chuàng)建會加入到EventQueue隊列中(網(wǎng)絡(luò)上說這個這種情況下是加入到微隊列這是不對的蜻展,你多做幾次試驗就知道了喉誊,這個肯定是加入到了EventQueue中), 所以打印print("task6”)也會先執(zhí)行(這個也說明了then串起來的那些回調(diào)函數(shù)在Future完成的時候會被立即執(zhí)行纵顾,也就是說它們是同步執(zhí)行)
task1也被添加到EventQueue中被執(zhí)行伍茄;
一次在EventQueue中添加task2、task3施逾、task5被執(zhí)行敷矫,再執(zhí)行task4
task3的打印執(zhí)行完后,調(diào)用scheduleMicrotask會在執(zhí)行完這次的EventQueue后會立即執(zhí)行汉额,所以在task5后執(zhí)行task4(這還是因為then串起來的那些回調(diào)函數(shù)在Future完成的時候會被立即執(zhí)行)
task8曹仗、task9、task10一次添加到EventQueue被執(zhí)行蠕搜;
好了通過上面的例子我相信你已經(jīng)對于Dart的Future的執(zhí)行過程已經(jīng)理解很深了怎茫,那么我們繼續(xù)看看Dart中async,await的內(nèi)容:
在Dart1.9中加入了async和await關(guān)鍵字妓灌,有了這兩個關(guān)鍵字轨蛤,我們可以更簡潔的編寫異步代碼,而不需要調(diào)用Future相關(guān)的API虫埂。他們允許你像寫同步代碼一樣寫異步代碼和不需要使用Future接口祥山。相當(dāng)于都Future相關(guān)API接口的另一種封裝,提供了一種更加簡便的操作Future相關(guān)API的方法掉伏,這個概念與我們JavaScript里面的async和await關(guān)鍵字實在是像極了缝呕,可以說一模一樣澳窑,JavaScript里面的async和await其實也是對于Promise的封裝
Dart將 async 關(guān)鍵字作為方法聲明的后綴時,具有如下意義:
被修飾的方法會將一個 Future 對象作為返回值(這個JavaScript中也是一樣的概念岳颇,JavaScript的async會返回一個Promise)
該方法會同步執(zhí)行其中的方法的代碼直到第一個 await 關(guān)鍵字照捡,然后它暫停該方法其他部分的執(zhí)行颅湘;(這個也與JavaScript中是一樣的)
-
下面說兩個細(xì)節(jié)(網(wǎng)絡(luò)上很多文章都是點到為止话侧,這兩個細(xì)節(jié)一般不怎么提到):
- await后面代碼可以是兩種情況,一種是最常見的await等待的是一個Future(也就是另一個async函數(shù))闯参,那么這種情況會阻塞這個async函數(shù)return的那一句話瞻鹏,轉(zhuǎn)而執(zhí)行await函數(shù)外面的代碼,這種情況是最為常見的現(xiàn)象(而且這種情況下與JavaScript的await執(zhí)行情況是一模一樣的)鹿寨,那什么時候輪到await函數(shù)后面的代碼執(zhí)行呢新博,其實普通的async函數(shù)return后面的代碼Dart會把它轉(zhuǎn)變?yōu)橐粋€微任務(wù)的Future,輪到微任務(wù)執(zhí)行的時間就會開始執(zhí)行脚草,下面我們看一下代碼示例 :
import "dart:async";
foo() async {
print('foo E');
String value = await fun();
print('foo X $value');
}
fun() async{
print('fun');
return 'fun';
}
main() {
print('main E');
foo();
Future.value(333).then((a)=> print(a));
print("main X");
}
//main E
//foo E
//fun
//main X
//foo X fun
//333
執(zhí)行順序:
- 首先執(zhí)行打印’main E' 赫悄;然后進入foo()函數(shù)這是一個異步函數(shù),首先打印里面的同步代碼foo E馏慨;
- 然后await fun()函數(shù)埂淮,進入到fun函數(shù)打印同步代碼’fun’ ,然后return ’fun‘ 字符串写隶,這里會阻塞await后面的代碼(后面的代碼變成一個微隊列任務(wù)執(zhí)行)倔撞;
- 然后轉(zhuǎn)而執(zhí)行async外面的代碼 Future.value(333).then((a)=> print(a)); 也加入到微任務(wù)執(zhí)行;
- 最后面打印’main X‘慕趴;然后開始按照順序 執(zhí)行微任務(wù)痪蝇,所以會先打印’foo X fun‘,再打印’333‘
我再把 代碼稍微改一下再來看看執(zhí)行效果:
import "dart:async";
foo() async {
print('foo E');
String value = await fun();
print('foo X $value');
}
fun() {
print('fun');
return Future(()=>'fun Funture');
}
main() {
print('main E');
foo();
Future.value(333).then((a)=> print(a));
print("main X");
}
//main E
//foo E
//fun
//main X
//333
//foo X fun Funture
去掉了fun的async關(guān)鍵字冕房,并且改了fun函數(shù)里面的return的代碼躏啰,讓他返回一個future,執(zhí)行順序為什么變了呢耙册,很簡單啊丙唧,因為return的Future是通過構(gòu)造函數(shù)實現(xiàn)的,這種Future就是一個EventQueue事件觅玻,他的優(yōu)先級在微隊列下面想际,所以最后執(zhí)行
下面聊await后面代碼的另一種情況:
- await后面代碼還可以是等待一個表達式,那么這種情況會阻塞await那一句話溪厘,轉(zhuǎn)而執(zhí)行await函數(shù)外面的代碼胡本,(這種情況下也是與JavaScript的await等待表達式執(zhí)行情況是一模一樣的,我經(jīng)常懷疑Dart肯定借鑒了很多JavaScript的優(yōu)秀設(shè)計)畸悬,那什么時候輪到await函數(shù)后面的代碼執(zhí)行呢侧甫,await后面的代碼Dart會把它轉(zhuǎn)變?yōu)橐粋€微任務(wù)的Future,輪到微任務(wù)執(zhí)行的時間就會開始執(zhí)行,我們再來看一下代碼示例 :
import "dart:async";
foo() async {
print('foo E');
String value = await '100';
print('foo X $value');
}
main() {
print('main E');
Future(() => 111).then((val) => print(val));
Future.value(333).then((a)=> print(a));
foo();
print("main X");
}
//main E
//foo E
//main X
//333
//foo X 100
//111
簡單的講解下執(zhí)行順序:
- 首先打印‘main E’ 披粟,然后要print(111)放入到EventQueue咒锻,
- 然后遇到print(333)放入微隊列,
- 最后遇到await 100 表達式守屉,按照上面說的應(yīng)該放到微隊列任務(wù)里面惑艇,等主線程打印’main X’ 然后執(zhí)行微隊列的任務(wù)打印333,然后再打印foo X 100拇泛,最后微任務(wù)執(zhí)行完畢再執(zhí)行EventQueue的任務(wù)打印111
好了滨巴,現(xiàn)在對于flutter的Future await事件隊列, 微任務(wù)目前已經(jīng)講解完畢了俺叭,我們先從異步運行的基礎(chǔ)(Event Loop)開始恭取,首先介紹了異步運行機制,到Future熄守;再到 async和await蜈垮,通過了幾個小的例子了解了Flutter/Dart中的異步運行機制背后的原理是什么,對于一直從事Native開發(fā)裕照,不太了解JavaScript的同學(xué)來講攒发,這個異步機制和原生開發(fā)還是有很大的不同,需要多加的練習(xí)以及思考才能 熟練運用牍氛,好了如果你喜歡本文的話幫忙點個贊晨继,如果你有任何疑問的話可以給我留言,歡迎一起探討問題···