Flutter中的異步編程--Future

本文章所寫的代碼github地址
Dart是一個在線程中運行的程序,這意味著:如果程序再執(zhí)行中遇到一個需要長時間執(zhí)行的操作,程序?qū)ㄗ?,這不就尷尬了嘛,為了避免這種尷尬的情況,可以使用異步操作使程序在等待一個耗時操作完成時繼續(xù)處理其他工作。在Dart中,可以使用Future對象來表示異步操作的結(jié)果。

Dart的消息循環(huán)機(jī)制

在進(jìn)入正題之前,我們先看一下Dart的消息循環(huán)機(jī)制:

Dart消息循環(huán)機(jī)制

簡單總結(jié)一下,詳細(xì)內(nèi)容可以看文章The Event Loop and Dart
Dart中事件循環(huán)的一些主要概念:

  • Dart從兩個隊列執(zhí)行任務(wù):event事件隊列microtask微任務(wù)隊列,優(yōu)先處理microtask微任務(wù)隊列;
  • Dart的方法是不會被其他Dart代碼打斷的,當(dāng)main執(zhí)行完成后,main isolate[1]的線程就會去逐一處理消息隊列中的消息。
  • 事件隊列具有來自Dart(Future,Timer,isolate[1] Message等)和系統(tǒng)(用戶輸入,I/O等);
  • 微任務(wù)隊列目前僅包含來自Dart(這句話我也不太懂);
  • 事件循環(huán)會優(yōu)先處理微任務(wù)隊列,microtask清空之后才開始處理event事件隊列。
  • 一旦兩個隊列都為空,則應(yīng)用程序已經(jīng)完成工作,并且(取決于其嵌入程序)可以退出。
  • main() 函數(shù)以及微任務(wù)和事件隊列中的所有項目都在Dart應(yīng)用程序的main ioslate[1]上運行。

什么是Future

Future<T>(T表示泛型)表示一個指定類型的異步操作結(jié)果(不需要結(jié)果的情況可以使用Future<void>),當(dāng)一個返回Future對象的函數(shù)被調(diào)用時:

  1. 將函數(shù)放入隊列等待執(zhí)行并返回一個未完成的Future對象
  2. 當(dāng)函數(shù)執(zhí)行完成,F(xiàn)uture對象中會被賦值執(zhí)行的結(jié)果,已經(jīng)執(zhí)行的狀態(tài)
  3. 運行狀態(tài)(pending),白噢是任務(wù)還未完成,也沒有返回值
  4. 完成狀態(tài)(completed),表示任務(wù)已經(jīng)完成(無論失敗還是成功)
    例如:



    觀看程序輸出,首先執(zhí)行完main函數(shù),然后再去執(zhí)行任務(wù)棧中的內(nèi)容,在該例中也就是我們使用Future加入到event任務(wù)棧中,then中的方法會在Future處于完成態(tài)(completed)時立馬執(zhí)行,之后我們在詳細(xì)講解。
    Dart提供了幾種Future的創(chuàng)建方法,其中常有的有:

  factory Future(FutureOr<T> computation()) {
    _Future<T> result = new _Future<T>();
    Timer.run(() {
      try {
        result._complete(computation());
      } catch (e, s) {
        _completeWithErrorCallback(result, e, s);
      }
    });
    return result;
  }

future.dart中所使用的就是這種方式創(chuàng)建的Future。
其他創(chuàng)建Future的方式包括:

  • Future.value() :返回一個指定值的Future
  • Future.delayed() :返回一個延時執(zhí)行的Future


    兩種方式的執(zhí)行效果

這端代碼執(zhí)行了兩個分支:

  • main()方法
  • event()隊列
Future中的任務(wù)調(diào)度

前面說過:當(dāng)Future執(zhí)行完成后,then()注冊的回調(diào)函數(shù)會立即執(zhí)行,但是then中的函數(shù)并不會被添加到事件隊列中,只是在事件隊列中的任務(wù)被執(zhí)行完成后才被立刻執(zhí)行(可以理解為:將網(wǎng)絡(luò)請求放在隊列中進(jìn)行執(zhí)行,拿到結(jié)果后在then中刷新UI)。


首先,人物隊列是以FIFO的方式進(jìn)行,f1,f2,f3依次被加入到任務(wù)棧,then()注冊的函數(shù)并不會被添加到隊列,也不會直接運行。當(dāng)任務(wù)棧中的人物被執(zhí)行后,立刻執(zhí)行then中的函數(shù),依次類推,可以看到,then中回調(diào)函數(shù)執(zhí)行順序并不取決于注冊的順序,而僅僅與其Future被加入到任務(wù)棧的順序有關(guān)。
注意:new Future(()=>null)和new Future(null)有本質(zhì)的區(qū)別,一個函數(shù)題為空,什么都不做,一個是參數(shù)為空,不存在函數(shù)。
稍微修改一下上例的代碼:

是否會對結(jié)果有所疑惑呢,先看一下then的定義吧

Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError});

這里設(shè)計到兩個關(guān)健點:

  • 如果Future在then被調(diào)用之前已經(jīng)完成,那么then中的函數(shù)會被作為任務(wù)添加到microtask隊列中;
  • then會返回新的Future,并且該Future在onValue(then中注冊的回調(diào)函數(shù))或者onError被執(zhí)行時就已經(jīng)處于完成狀態(tài)了。
  • 如果onValue(回調(diào)函數(shù))返回值為一個Future,那么then返回的Future將會在onValue返回的future執(zhí)行完成后處于完成狀態(tài)
    關(guān)于后面兩點:



    其中,每個then都會返回一個新的Future,而該future會在onValue,也就是回調(diào)函數(shù)執(zhí)行時處于完成狀態(tài),然后立即執(zhí)行該future的回調(diào)函數(shù)。


注意,then方法本身會返回一個Future。在then中的函數(shù)也返回一個Future,而then所返回的Future會緊跟著函數(shù)返回的future之后處于完成狀態(tài)再執(zhí)行后續(xù)的回調(diào)函數(shù)。

總結(jié)一下:
  • 當(dāng)Future任務(wù)完成后,then()注冊的回調(diào)函數(shù)會立即執(zhí)行。需注意的是,then()注冊的函數(shù)并不會添加到事件隊列中,回調(diào)函數(shù)只是在事件循環(huán)中任務(wù)完成后被調(diào)用。
  • 如果Future在then()被調(diào)用之前已經(jīng)完成計算,那么任務(wù)會被添加到微任務(wù)隊列中,并且該任務(wù)會執(zhí)行then()中注冊的回調(diào)函數(shù)。
  • then會返回新的Future,并且該Future在onValue(then中注冊的回調(diào)函數(shù))或者onError被執(zhí)行時就已經(jīng)處于完成狀態(tài)了。
  • 如果onValue(回調(diào)函數(shù))返回值為一個Future,那么then返回的Future將會在onValue返回的future執(zhí)行完成后處于完成狀態(tài)

如果處理異步操作的結(jié)果

包括上面提到的then,有三種方法處理Future的結(jié)果:

  • then:處理操作執(zhí)行結(jié)果或者錯誤并返回一個新的Future
  • catchError: 注冊一個處理錯的回調(diào)
  • whenComplete:類似final,無論錯誤還是正確,F(xiàn)uture執(zhí)行結(jié)束后總是被調(diào)用

then中的onError只能處理當(dāng)前Future中的錯誤,而catchError能處理整條調(diào)用鏈上的任何錯誤。


async和await

上面說了Future的基本用法,以及使用Future API處理數(shù)據(jù)的方法,但是這種方法存在一個問題:使用鏈?zhǔn)秸{(diào)用的方式把多個future連接在一起,會嚴(yán)重降低代碼的可讀性。
可以使用async和await關(guān)鍵字實現(xiàn)異步的功能。async和await可以幫助我們像寫同步代碼一樣編寫異步代碼



**注意:await只能在async函數(shù)里出現(xiàn) **
要想改變異步代碼,只需要在函數(shù)中添加async關(guān)鍵字

String getAString() {
  return "我是一個字符串";
}
## 改寫為異步代碼
Future<String> getAString() async{
  return "我是一個字符串";
}

需要注意的是,在普通函數(shù)中,return返回的為T,那么async函數(shù)中返回 的是Future<T>。但是并不需要顯示的去指定返回的類型,Dart會自動將返回值包裝成Future對象。但是,如果原函數(shù)返回的為Future<T>,在async函數(shù)中返回的仍然是Future<T>,若async函數(shù)沒有返回值,那么Dart會返回一個null值的Future。



注意觀察代碼的執(zhí)行順序,函數(shù)按照順序執(zhí)行,首先執(zhí)行test9函數(shù),接著按照順序執(zhí)行firstString()、secondString()、 thirdString().Future.delayed并不會阻礙任何代碼的執(zhí)行,這符合上文中講的非阻塞任何代碼的執(zhí)行,F(xiàn)uture并不會阻塞它所在函數(shù)的執(zhí)行。
我們稍微修改一下代碼:



對比兩次結(jié)果不難發(fā)現(xiàn),async和await關(guān)鍵字使得原本非阻塞式的函數(shù)變得同步了,成了阻塞函數(shù)了。函數(shù)遇到Future,在其未執(zhí)行完之前一直處于阻塞狀態(tài)。但是test10函數(shù)依舊正常執(zhí)行。并不會被async函數(shù)所阻塞。async和await只會作用當(dāng)前函數(shù),并不會對其他外部函數(shù)造成執(zhí)行上的影響。
await也可以幫助我們在執(zhí)行下個語句之前確保當(dāng)前語句執(zhí)行完畢:

本文檔參考維特or卡頓大佬的Future博文


  1. isolate解析 ? ? ?

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

相關(guān)閱讀更多精彩內(nèi)容

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