Flutter原理篇:聊一聊future,await,事件隊列,微任務(wù)

上一篇我們提到了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)度改衩。

再來看一張很熟悉的圖片:

image.png

Dart的事件循環(huán)的運行遵循以下規(guī)則:

  1. 首先處理所有微任務(wù)隊列里的微任務(wù)岖常。
  2. 處理完所有微任務(wù)以后。從事件隊列里取1個事件進行處理葫督。
  3. 回到微任務(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é)下:

  1. main函數(shù)先執(zhí)行骚灸,所以main start和main end先執(zhí)行,沒有任何問題慌植;

  2. main函數(shù)執(zhí)行過程中甚牲,會將一些任務(wù)分別加入到EventQueue和MicrotaskQueue中;

  3. task7通過scheduleMicrotask函數(shù)調(diào)用蝶柿,所以它被最早加入到MicrotaskQueue鳖藕,會被先執(zhí)行,task11通過Future.value()創(chuàng)建也是會加入到MicrotaskQueue只锭,接著執(zhí)行

  4. 然后開始執(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í)行)

  5. task1也被添加到EventQueue中被執(zhí)行伍茄;

  6. 一次在EventQueue中添加task2、task3施逾、task5被執(zhí)行敷矫,再執(zhí)行task4

  7. task3的打印執(zhí)行完后,調(diào)用scheduleMicrotask會在執(zhí)行完這次的EventQueue后會立即執(zhí)行汉额,所以在task5后執(zhí)行task4(這還是因為then串起來的那些回調(diào)函數(shù)在Future完成的時候會被立即執(zhí)行

  8. 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é)一般不怎么提到):

    1. 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后面代碼的另一種情況:

  1. 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í)以及思考才能 熟練運用牍氛,好了如果你喜歡本文的話幫忙點個贊晨继,如果你有任何疑問的話可以給我留言,歡迎一起探討問題···

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搬俊,一起剝皮案震驚了整個濱河市紊扬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唉擂,老刑警劉巖餐屎,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異玩祟,居然都是意外死亡腹缩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門空扎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來藏鹊,“玉大人,你說我怎么就攤上這事转锈∨坦眩” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵撮慨,是天一觀的道長竿痰。 經(jīng)常有香客問我脆粥,道長,這世上最難降的妖魔是什么影涉? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任变隔,我火速辦了婚禮,結(jié)果婚禮上蟹倾,老公的妹妹穿的比我還像新娘匣缘。我一直安慰自己,他們只是感情好喊式,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布孵户。 她就那樣靜靜地躺著萧朝,像睡著了一般岔留。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上检柬,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天献联,我揣著相機與錄音,去河邊找鬼何址。 笑死里逆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的用爪。 我是一名探鬼主播原押,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼偎血!你這毒婦竟也來了诸衔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤颇玷,失蹤者是張志新(化名)和其女友劉穎笨农,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帖渠,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡谒亦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了空郊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片份招。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖狞甚,靈堂內(nèi)的尸體忽然破棺而出锁摔,到底是詐尸還是另有隱情,我是刑警寧澤入愧,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布鄙漏,位于F島的核電站嗤谚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏怔蚌。R本人自食惡果不足惜巩步,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桦踊。 院中可真熱鬧椅野,春花似錦、人聲如沸籍胯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杖狼。三九已至炼蛤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝶涩,已是汗流浹背理朋。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绿聘,地道東北人嗽上。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像熄攘,于是被迫代替她去往敵國和親兽愤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容