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


由于前面的HTTP請(qǐng)求用到了異步操作,不少小伙伴都被這個(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請(qǐng)求返回值為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請(qǐng)求的數(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簡(jiǎn)單科普


前面?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ī)場(chǎng)的公共排隊(duì)大廳。

? ??2.microtask queue只在當(dāng)前isolate的任務(wù)隊(duì)列中排隊(duì),優(yōu)先級(jí)高于event queue,好比機(jī)場(chǎng)里的某個(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ù)測(cè)任務(wù)執(zhí)行的順序,但你無法準(zhǔn)確的預(yù)測(cè)到事件循環(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請(qǐng)求控件)的get函數(shù)、RefreshIndicator(下拉手勢(shì)刷新控件)的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)轭愋筒黄ヅ?,IDE會(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提供了一套非常簡(jiǎn)潔的解決方案:

//案例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)場(chǎng)子,加入進(jìn)來沒事就寫寫APP掙點(diǎn)外快(這個(gè)真的有),順便翻譯翻譯官方英文原稿拉一票粉絲,一舉多得何樂而不為呢。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容