由于前面的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 queue和microtask 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 queue和microtask 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 思否