Flutter中異步async, await 和 Future的使用技巧


由于前面的HTTP請求用到了異步操作颤陶,不少小伙伴都被這個(gè)問題折了下腰臀栈,今天總結(jié)分享下實(shí)戰(zhàn)成果。Dart是一個(gè)單線程的語言玫锋,遇到有延遲的運(yùn)算(比如IO操作蛾茉、延時(shí)執(zhí)行)時(shí),線程中按順序執(zhí)行的運(yùn)算就會(huì)阻塞撩鹿,用戶就會(huì)感覺到卡頓谦炬,于是通常用異步處理來解決這個(gè)問題。當(dāng)遇到有需要延遲的運(yùn)算(async)時(shí)节沦,將其放入到延遲運(yùn)算的隊(duì)列(await)中去吧寺,把不需要延遲運(yùn)算的部分先執(zhí)行掉窜管,最后再來處理延遲運(yùn)算的部分。

async和await

首先看一個(gè)案例:

//HTTP的get請求返回值為Future<String>類型稚机,即其返回值未來是一個(gè)String類型的值

getData( )async{

????//async關(guān)鍵字聲明該函數(shù)內(nèi)部有代碼需要延遲執(zhí)行

????returnawaithttp.get(Uri.encodeFull(url), headers: {"Accept":"application/json"});//await關(guān)鍵字聲明運(yùn)算為延遲執(zhí)行幕帆,然后return運(yùn)算結(jié)果

}

然后我們調(diào)用這個(gè)函數(shù),想獲取其結(jié)果:

String data= getData( );

在書寫時(shí)赖条,在IDE中這個(gè)代碼是沒有問題的失乾,但是當(dāng)我們運(yùn)行這段代碼時(shí),就報(bào)錯(cuò)了:

為什么呢纬乍?因?yàn)閐ata是String類型碱茁,而函數(shù)getData()是一個(gè)異步操作函數(shù),其返回值是一個(gè)await延遲執(zhí)行的結(jié)果仿贬。在Dart中纽竣,有await標(biāo)記的運(yùn)算,其結(jié)果值都是一個(gè)Future對(duì)象茧泪,F(xiàn)uture不是String類型蜓氨,所以就報(bào)錯(cuò)了。

那如果這樣的話队伟,我們就沒法獲取到延遲執(zhí)行的結(jié)果了穴吹?當(dāng)然可以,Dart規(guī)定有async標(biāo)記的函數(shù)嗜侮,只能由await來調(diào)用港令,比如這樣:

String data =await getData();

但是要使用await,必須在有async標(biāo)記的函數(shù)中運(yùn)行锈颗,否則這個(gè)await會(huì)報(bào)錯(cuò):


于是顷霹,我們要為這個(gè)給data賦值的語句加一個(gè)async函數(shù)的包裝:

String data;

setData() async {?

????data =await getData(); //getData()延遲執(zhí)行后賦值給data

}

上面這種方法一般用于調(diào)用封裝好的異步接口,比如getData()被封裝到了其他dart文件击吱,通過使用async函數(shù)對(duì)其調(diào)取使用

再或者淋淀,我們?nèi)サ鬭sync函數(shù)的包裝,在getData()中直接完成data變量的賦值:

String data;

getData() async {?

????data =await http.get(Uri.encodeFull(url), headers: {"Accept":"application/json"}); //延遲執(zhí)行后賦值給data

}

這樣姨拥,data就獲取到HTTP請求的數(shù)據(jù)了绅喉。就這樣就完了渠鸽?是滴叫乌,只要記住兩點(diǎn):

? ?1. await關(guān)鍵字必須在async函數(shù)內(nèi)部使用

? ?2. 調(diào)用async函數(shù)必須使用await關(guān)鍵字

PS:await關(guān)鍵字真的很形象,等一等的意思徽缚,就是說憨奸,既然你運(yùn)行的時(shí)候都要等一等,那我調(diào)用的時(shí)候也等一等吧

Future簡單科普


前面?zhèn)€講到過凿试,直接return await ...的時(shí)候排宰,實(shí)際上返回的是一個(gè)延遲計(jì)算的Future對(duì)象似芝,這個(gè)Future對(duì)象是Dart內(nèi)置的,有自己的隊(duì)列策略板甘,我們就來聊聊這個(gè)Future党瓮。

先啰嗦一些關(guān)于Dart在線程方面的知識(shí)。

Dart是基于單線程模型的語言盐类。在Dart也有自己的進(jìn)程(或者叫線程)機(jī)制寞奸,名叫isolate。APP的啟動(dòng)入口main函數(shù)就是一個(gè)isolate在跳。玩家也可以通過引入import 'dart:isolate'創(chuàng)建自己的isolate枪萄,對(duì)多核CPU的特性來說,多個(gè)isolate可以顯著提高運(yùn)算效率猫妙,當(dāng)然也要適當(dāng)控制isolate的數(shù)量瓷翻,不應(yīng)濫用,否則走火入魔自廢武功割坠。有一個(gè)很重要的點(diǎn)齐帚,Dart中isolate之間無法直接共享內(nèi)存,不同的isolate之間只能通過isolate?API進(jìn)行通信韭脊,當(dāng)然本篇的重點(diǎn)在于Future童谒,不展開講isolate,心急的小伙伴可以參考官方閱讀理解或者參考大神tain335人肉翻譯沪羔。

Dart線程中有一個(gè)消息循環(huán)機(jī)制(event loop)和兩個(gè)隊(duì)列(event queuemicrotask queue)饥伊。

? ? 1.event queue包含所有外來的事件:I/O,mouse events蔫饰,drawing events琅豆,timers,isolate之間的message等篓吁。任意isolate中新增的event(I/O茫因,mouse events,drawing events杖剪,timers冻押,isolate的message)都會(huì)放入event queue中排隊(duì)等待執(zhí)行,好比機(jī)場的公共排隊(duì)大廳盛嘿。

? ??2.microtask queue只在當(dāng)前isolate的任務(wù)隊(duì)列中排隊(duì)洛巢,優(yōu)先級(jí)高于event queue,好比機(jī)場里的某個(gè)VIP候機(jī)室次兆,總是VIP用戶先登機(jī)了稿茉,才開放公共排隊(duì)入口。

如果在event中插入microtask,當(dāng)前event執(zhí)行完畢即可插隊(duì)執(zhí)行microtask漓库。如果沒有microtask恃慧,就沒辦法插隊(duì)了,也就是說渺蒿,microtask queue的存在為Dart提供了給任務(wù)隊(duì)列插隊(duì)的解決方案痢士。

當(dāng)main方法執(zhí)行完畢退出后,event loop就會(huì)以FIFO(先進(jìn)先出)的順序執(zhí)行microtask茂装,當(dāng)所有microtask執(zhí)行完后它會(huì)從event queue中取事件并執(zhí)行良瞧。如此反復(fù),直到兩個(gè)隊(duì)列都為空训唱,如下流程圖:


注意:當(dāng)事件循環(huán)正在處理microtask的時(shí)候褥蚯,event queue會(huì)被堵塞。這時(shí)候app就無法進(jìn)行UI繪制况增,響應(yīng)鼠標(biāo)事件和I/O等事件赞庶。胡亂插隊(duì)也是有代價(jià)的~

雖然你可以預(yù)測任務(wù)執(zhí)行的順序,但你無法準(zhǔn)確的預(yù)測到事件循環(huán)何時(shí)會(huì)處理你期望的任務(wù)澳骤。例如當(dāng)你創(chuàng)建一個(gè)延時(shí)1s的任務(wù)歧强,但在排在你之前的任務(wù)結(jié)束前事件循環(huán)是不會(huì)處理這個(gè)延時(shí)任務(wù)的,也就是或任務(wù)執(zhí)行可能是大于1s的为肮。

OK摊册,了解以上信息之后,再來回到Future颊艳,小伙伴可能已經(jīng)被繞暈了茅特。

Future就是event,很多Flutter內(nèi)置的組件比如前幾篇用到的Http(http請求控件)的get函數(shù)棋枕、RefreshIndicator(下拉手勢刷新控件)的onRefresh函數(shù)都是event白修。每一個(gè)被await標(biāo)記的句柄也是一個(gè)event,每創(chuàng)建一個(gè)Future就會(huì)把這個(gè)Future扔進(jìn)event queue中排隊(duì)等候安檢~

什么重斑?那microtask呢兵睛?當(dāng)然不會(huì)忘了這個(gè),scheduleMicrotask窥浪,用法和Future基本一樣祖很。

為什么要用Future?


前面講到漾脂,用async和await組合假颇,即可向event queue中插入event實(shí)現(xiàn)異步操作,好像Future的存在有些多余的感覺符相,剛開始我本人也有這樣的疑惑拆融,且往下看。

當(dāng)定義Flutter函數(shù)時(shí)啊终,還可以指定其運(yùn)行結(jié)果返回值的類型镜豹,以提高代碼的可讀性:

//定義了返回結(jié)果值為String類型

Future<String>?getDatas(String category) async {

????var request? = await _httpClient.getUrl(Uri.parse(url));

????var response = await request.close();

????return await response.transform(utf8.decoder).join();

}

run() async {

????int data = await getDatas('keji');? ? //因?yàn)轭愋筒黄ヅ洌琁DE會(huì)報(bào)錯(cuò)

}

Future最主要的功能就是提供了鏈?zhǔn)秸{(diào)用蓝牲。熟悉ES6語法的小伙伴樂開了花趟脂,鏈?zhǔn)秸{(diào)用解決兩大問題:明確代碼執(zhí)行的依賴關(guān)系和實(shí)現(xiàn)異常捕獲。WTF?還不明白例衍?且看下面這些案例:

//案例1

funA( ) async{

? ????...set an important variable...

}

funB( ) async{

????await funA( );

? ...use the important variable...

}

main( ) async {?

????funB();?

}

//如果要想先執(zhí)行funA再執(zhí)行funB昔期,必須在funB中await funA();

//funB的代碼與funA耦合,將來如果funA廢掉或者改動(dòng)佛玄,funB中還需要經(jīng)過修改以適配變更硼一。

//案例2

funA( ) async{

????try{? ?

????????...set an important variable...?

????}catch(e){

????????do sth...?

????}finally{

????????do sth.else...?

????}

}

funB( ) async{

????try{

? ? ???? ...use the important variable...?

????}catch(e){

????????do sth...?

????}finally{

????????do sth. else...?

????}

}

main( ) async {

????await funA( );

????await funB();

}

//沒有明確體現(xiàn)出設(shè)置變量和使用變量之間的依賴關(guān)系,其他開發(fā)者難以理解你的代碼邏輯梦抢,代碼維護(hù)困難

//并且如果為了防止funA()或者funB()因發(fā)生異常導(dǎo)致程序崩潰

//要到funA()或者funB()中分別加入`try`般贼、`catch`、`finally`

為了解決上面的問題奥吩,Future提供了一套非常簡潔的解決方案:

//案例3

funA( ){

? ...set an important variable...? ? ? ?//設(shè)置變量

}

funB( ){

? ...use the important variable...? ? ?//使用變量

}

main( ){

????new Future.then(funA()).then(funB());????// 明確表現(xiàn)出了后者依賴前者設(shè)置的變量值

????new Future.then(funA()).then((_) {newFuture(funB())});????//還可以這樣用

????//鏈?zhǔn)秸{(diào)用哼蛆,捕獲異常

????new Future.then(funA(),onError: (e) {handleError(e); }).then(funB(),onError: (e) {handleError(e); })?

}


案例3的玩法是async和await無法企及的,因此掌握Future還是很有必要滴霞赫。當(dāng)然了腮介,Future的玩法不僅僅局限于案例3,還有很多有趣的玩法端衰,包括和microtask對(duì)象scheduleMicrotask配合使用叠洗,我這里就不一一介紹了,大家參考大神tain335人肉翻譯或者官網(wǎng)閱讀理解吧旅东。

總結(jié)


Dart的isolate中加入了event queuemicrotask queue后惕味,有了一點(diǎn)協(xié)程的感覺,或許這就是Flutter為啥在性能上敢和原生開發(fā)叫板的原因之一吧玉锌。本篇的內(nèi)容比較抽象名挥,如果還是有不明白的小伙伴,歡迎留言提問主守,我盡量回答禀倔,哈哈哈,就醬参淫,歡迎加入到Flutter圈子flutter 中文社區(qū)(官方QQ群:338252156)救湖,群里有前后端及全棧各路大神鎮(zhèn)場子,加入進(jìn)來沒事就寫寫APP掙點(diǎn)外快(這個(gè)真的有)涎才,順便翻譯翻譯官方英文原稿拉一票粉絲鞋既,一舉多得何樂而不為呢力九。

轉(zhuǎn)載:flutter實(shí)戰(zhàn)5:異步async、await和Future的使用技巧_flutter系列 - SegmentFault 思否

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邑闺,一起剝皮案震驚了整個(gè)濱河市跌前,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌陡舅,老刑警劉巖抵乓,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異靶衍,居然都是意外死亡灾炭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門颅眶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜈出,“玉大人,你說我怎么就攤上這事涛酗√投校” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵煤杀,是天一觀的道長眷蜈。 經(jīng)常有香客問我,道長沈自,這世上最難降的妖魔是什么酌儒? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮枯途,結(jié)果婚禮上忌怎,老公的妹妹穿的比我還像新娘。我一直安慰自己酪夷,他們只是感情好榴啸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晚岭,像睡著了一般鸥印。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坦报,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天库说,我揣著相機(jī)與錄音,去河邊找鬼片择。 笑死潜的,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的字管。 我是一名探鬼主播啰挪,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼信不,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亡呵?” 一聲冷哼從身側(cè)響起抽活,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎政己,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掏愁,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡歇由,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了果港。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沦泌。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辛掠,靈堂內(nèi)的尸體忽然破棺而出谢谦,到底是詐尸還是另有隱情,我是刑警寧澤萝衩,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布回挽,位于F島的核電站,受9級(jí)特大地震影響猩谊,放射性物質(zhì)發(fā)生泄漏千劈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一牌捷、第九天 我趴在偏房一處隱蔽的房頂上張望墙牌。 院中可真熱鬧,春花似錦暗甥、人聲如沸喜滨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虽风。三九已至,卻和暖如春寄月,著一層夾襖步出監(jiān)牢的瞬間焰情,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工剥懒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留内舟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓初橘,卻偏偏與公主長得像验游,于是被迫代替她去往敵國和親充岛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345