Flutter Future 詳解

語雀

什么是 Future

FutureDart中提供的一個抽象類、泛型類,它用于封裝一段在將來會被執(zhí)行的代碼邏輯。構(gòu)造一個Future就會向event queue中添加一條記錄。如果把event queue類比Android中的message queue的話,那么可以簡單的把Future類比為Android中的Message.只不過Future中包含了需要完成的整個操作。并且利用Future的then和whenComplete方法可以指定在完成Future包含的操作后立馬執(zhí)行另一段邏輯。 關(guān)于Future的更多詳情可以參閱官方文檔。

繼續(xù)之前我感覺很有必要了解下Dart中的消息機制 ?。。?!

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

Dart的單線程執(zhí)行

當一個Dart的方法開始執(zhí)行時,他會一直執(zhí)行直至達到這個方法的退出點。換句話說Dart的方法是不會被其他Dart代碼打斷的。

Note:一個Dart的命令行應(yīng)用可以通過創(chuàng)建isolates來達到并行運行的目的。isolates之間不會共享內(nèi)存,它們就像幾個運行在不同進程中的app,中能通過傳遞message來進行交流。出了明確指出運行在額外的isolates或者workers中的代碼外,所有的應(yīng)用代碼都是運行在應(yīng)用的main isolate中。要了解更多相關(guān)內(nèi)容,可以查看https://www.dartlang.org/arti...

正如下圖所示,當一個Dart應(yīng)用開始的標志是它的main isolate執(zhí)行了main方法。當main方法退出后,main isolate的線程就會去逐一處理消息隊列中的消息。

image

isolate

Dart是基于單線程模型的語言。在Dart中有一個很重要的概念叫isolate,它其實就是一個線程或者進程的實現(xiàn),具體取決于Dart的實現(xiàn)。默認情況下,我們用Dart寫的應(yīng)用都是運行在****main isolate****中的(可以對應(yīng)理解為Android中的main thread)。當然我們在必要的時候也可以通過isolate API創(chuàng)建新的isolate,多個isolate可以更好的利用多核CPU的特性來提高效率。但是要注意的是在Dart中isolate之間是無法直接共享內(nèi)存的,不同的isolate之間只能通過isolate API進行通信。因為 isolate 是單線程實體,所以 isolate中的代碼是按順序執(zhí)行的。關(guān)于isolate更多詳情可以參閱官方文檔

Dart的消息循環(huán)和消息隊列

一個Dart應(yīng)用有一個消息循環(huán)(event loop) 和兩個消息隊列(event queue 和 microtask queue)。

每個isolate都有單獨的event loop

event隊列包含所有外來的事件:I/O,mouse events,drawing events,timers,isolate之間的message等。

microtask 隊列在Dart中是必要的,因為有時候事件處理想要在稍后完成一些任務(wù)但又希望是在執(zhí)行下一個事件消息之前。

event隊列包含Dart和來自系統(tǒng)其它位置的事件。但microtask隊列只包含來自當前isolate的內(nèi)部代碼。

正如下面的流程圖,當main方法退出后,event循環(huán)就開始它的工作。首先它會以FIFO的順序執(zhí)行micro task,當所有micro task執(zhí)行完后它會從event 隊列中取事件并執(zhí)行。如此反復(fù),直到兩個隊列都為空。

[圖片上傳失敗...(image-da7b58-1590043915319)]

Dart 中的代碼執(zhí)行優(yōu)先級可以分為三個級別:

  1. 在 Main 中寫代碼將最先執(zhí)行;
  2. 執(zhí)行完 Main 中的代碼,然后會檢查并執(zhí)行 Microtask Queue 中的任務(wù), 通常使用 scheduleMicrotask 將事件添加到 MicroTask Queue 中;
  3. 最后執(zhí)行 EventQueue 隊列中的代碼,通常使用 Future 向 EventQueue加入事件,也可以使用 async 和 await 向 EventQueue 加入事件。

總結(jié):Dart 中事件的執(zhí)行順序:Main > MicroTask > EventQueue。

異步任務(wù)調(diào)度

當有代碼可以在后續(xù)任務(wù)執(zhí)行的時候,有兩種方式,通過dart:async這個Lib中的API即可:

  • 使用Future類,可以將任務(wù)加入到Event Queue的隊尾
  • 使用scheduleMicrotask函數(shù),將任務(wù)加入到Microtask Queue隊尾

當使用EventQueue時,需要考慮清楚,盡量避免microtask queue過于龐大,否則會阻塞其他事件的處理

image

大概了解上的知識點后,繼續(xù)Future:

Future狀態(tài)

  • pending - 執(zhí)行中;
  • completed - 執(zhí)行結(jié)束,分兩種情況要么成功要么失敗;

創(chuàng)建Future

常用的創(chuàng)建方法如下:

image.png

基本用法:

Future(FutureOr<T> computation())

Future 的構(gòu)造方法,創(chuàng)建一個基本的Future

var f1 = Future(() {
  print("1111");
});
print("2222");

//2222
//1111

打印順序是 2222-1111 這里準尋了Dart事件執(zhí)行順序 Main > MicroTask > EventQueue。

Future.value([FutureOr<T> value])

創(chuàng)建一個指定返回值的Future

Future.value("Success").then((value) => (print('123$value')));

Future.delayed()

創(chuàng)建一個延遲執(zhí)行的 Future

//延遲三秒執(zhí)行
 Future.delayed(Duration(seconds: 3), () {
   print("future delayed");
 });

Future.foreach(Iterable elements, FutureOr action(T element))

根據(jù)某個集合,創(chuàng)建一系列的Future,并且會按順序執(zhí)行這些Future

Future.forEach([1,2,3], (item) {
    return Future.delayed(Duration(seconds: 2),() {
      print(item);
    });
});

//1
//2
//3

根據(jù)集合 1,2,3 創(chuàng)建三個延遲的Future,每兩秒執(zhí)行一次. 一共6秒

Future<List<T>> wait<T>(Iterable<Future<T>> futures, {bool eagerError: false, void cleanUp(T successValue)})

用來等待多個future完成,并收集它們的結(jié)果. 結(jié)果有成功和失敗

var f1 = Future.delayed(Duration(seconds: 1),() => (1));
var f2 = Future.delayed(Duration(seconds: 2),() => (2));
var f3 = Future.delayed(Duration(seconds: 3),() => (3));
Future.wait([f1,f2,f3]).then((value) => print(value)).catchError(print);
//[1, 2, 3]

上面的代碼3個延遲的例子,三秒之后輸出 [1,2,3]. 和上面6秒的例子對比下, 一個 順序執(zhí)行多個future,一個是異步執(zhí)行多個future

var f1 = Future.delayed(Duration(seconds: 1),() => (1));
var f2 = Future.delayed(Duration(seconds: 2),() => throw "erro2");
var f3 = Future.delayed(Duration(seconds: 3),() => throw "erro3");
Future.wait([f1,f2,f3]).then((value) => print(value)).catchError(print);

//erro2

上面代碼,延遲2和3,都拋出異常,會直接拋出異常

Future.any(futures)

返回的是第一個執(zhí)行完成的future的結(jié)果,不會管這個結(jié)果是正確的還是error的。

Future.doWhile()

類似java中doWhile的用法,重復(fù)性地執(zhí)行某一個動作,直到返回false或者Future,退出循環(huán)。

使用場景:適用于一些需要遞歸操作的場景。

Future.microtask(FutureOr computation())

創(chuàng)建一個在microtask隊列運行的future。

microtask隊列的優(yōu)先級是比event隊列高的,而一般future是在event隊列執(zhí)行的,所以Future.microtask創(chuàng)建的future會優(yōu)先于其他future進行執(zhí)行。

Future(() => (print(11111)));
Future(() => (print(22222)));
Future.microtask(() => (print(33333)));

//flutter: 33333
//flutter: 11111
//flutter: 22222

處理Future結(jié)果

Flutter提供了下面三個方法,讓我們來注冊回調(diào),來監(jiān)聽處理Future的結(jié)果。

//處理完成時候的回調(diào),一般都是成功回調(diào)
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
//處理失敗的回調(diào),比如throw一個error就會走到這里
Future<T> catchError(Function onError, {bool test(Object error)});
//Future.whenComplete總是在Future完成后調(diào)用,不管Future的結(jié)果是正確的還是錯誤的。
Future<T> whenComplete(FutureOr action());

結(jié)合 async 和 await

Dart中他倆就是兄弟,一般都是一起出現(xiàn)的,來完成一個異步操作.

注意:await只能在async函數(shù)出現(xiàn)。 async函數(shù),返回值是一個Future對象,如果沒有返回Future對象,會自動將返回值包裝成Future對象。 捕捉錯誤,一般是使用try/catch機制來做異常處理。 await 一個future,可以拿到Future的結(jié)果,直到拿到結(jié)果,才執(zhí)行下一步的代碼。

void main() {
  print('t1:' + DateTime.now().toString());
  getData();
  print('t2:' + DateTime.now().toString());

}

getData() async {
    int result = await Future.delayed(Duration(milliseconds: 2000), () {
      return Future.value(123);
    });
    print('t3:' + DateTime.now().toString());
    print(result);
}

看上面的代碼,getData方法中,定義了一個延遲操作,執(zhí)行順序按照dart事件執(zhí)行順序是 t1-t2-t3-123,運行代碼能看到猜想是對的.

try {
  var result1 = await Future.value(1);
  print(result1);//輸出1
  var result2 = await Future.value(2);
  print(result2);//輸出2
} catch (e) {
    //捕捉錯誤
} finally {
  
}

上面的代碼和java一樣,利用try-catch-finally捕獲錯誤

執(zhí)行順序

Dart的事件順序應(yīng)該都明白了,加深下印象

void test1(){
  Future f2 = Future(() => null);
  Future f1 = Future(() => null);
  Future f3 = Future(() => null);
  f1.then((_) => print("1"));
  f2.then((_) => print("2"));
  f3.then((_) => print("3"));
}

結(jié)果:

flutter: 2

flutter: 1

flutter: 3

有疑問嗎,應(yīng)該沒有疑問,因為f2是先初始化的.所以先執(zhí)行的f2??梢?創(chuàng)建多個Future,執(zhí)行順序和和創(chuàng)建Future的先后順序有關(guān).和調(diào)用then的先后順序無關(guān)

void test2() {
  Future f1 = Future(() => null);
  Future f2 = Future(() => null);
  Future f3 = Future(() => null);

  f1.then((_) => print("f1 -> f1"));
  f3.then((_) {
    print("f3 -> f3");
    f1.then((_) => print("f3.then -> f1"));
  });
  f2.then((_) => print("f2 -> f2"));
}

結(jié)果:

flutter: f1 -> f1
flutter: f2 -> f2
flutter: f3 -> f3
flutter: f3.then -> f1

上面也是按照創(chuàng)建順序執(zhí)行的.

import 'dart:async';
main() {
  print('main #1 of 2');
  scheduleMicrotask(() => print('microtask #1 of 2'));

  new Future.delayed(new Duration(seconds:1),
                     () => print('future #1 (delayed)'));
  new Future(() => print('future #2 of 3'));
  new Future(() => print('future #3 of 3'));

  scheduleMicrotask(() => print('microtask #2 of 2'));

  print('main #2 of 2');
}


結(jié)果:

flutter: main #1 of 2
flutter: main #2 of 2
flutter: microtask #1 of 2
flutter: microtask #2 of 2
flutter: future #2 of 3
flutter: future #3 of 3
flutter: future #1 (delayed)

上面代碼的執(zhí)行順序,是完全按照 Dart事件順序去執(zhí)行的.

  1. main()方法
  2. microtask隊列
  3. event隊列.

main方法中的普通代碼都是同步執(zhí)行的,所以肯定是main打印先全部打印出來,等main方法結(jié)束后會開始檢查microtask中是否有任務(wù),若有則執(zhí)行,執(zhí)行完繼續(xù)檢查microtask,直到microtask列隊為空。所以接著打印的應(yīng)該是microtask的打印。最后會去執(zhí)行event隊列。由于有一個使用的delay方法,所以它的打印應(yīng)該是在最后的。

void main() {

  print('main 1');
  scheduleMicrotask(() => print('microtask 1'));

  new Future.delayed(new Duration(seconds:1),
          () => print('future 1'));

  new Future(() => print('future 2'))
      .then((_) => print('future #2.1'))
      .then((_) {
    print('future #2.2');
    scheduleMicrotask(() => print('microtask 4'));
  })
      .then((_) => print('future #2.3'));

  scheduleMicrotask(() => print('microtask 2'));

  new Future(() => print('future 3'))
      .then((_) => new Future(
          () => print('future #3.1 new')))
      .then((_) => print('future #3.2'));

  new Future(() => print('future 4'));
  scheduleMicrotask(() => print('microtask 3'));
  print('main 2');

}

執(zhí)行結(jié)果:
flutter: main 1
flutter: main 2
flutter: microtask 1
flutter: microtask 2
flutter: microtask 3
flutter: future 2
flutter: future #2.1
flutter: future #2.2
flutter: future #2.3
flutter: microtask 4
flutter: future 3
flutter: future 4
flutter: future #3.1 new
flutter: future #3.2
flutter: future 1

有點多,看著有點繞,我們來試著梳理一下:

  1. 首先執(zhí)行兩個main 1 和 main2 這個沒有任何問題
  2. 繼續(xù)執(zhí)行 microtask1,2,3 這個也沒有問題, 現(xiàn)在是 man1,2 microtask 1,2,3
  3. 走到delayed,現(xiàn)在應(yīng)該是 : man1,2 microtask 1,2,3 ... ... f1
  4. 繼續(xù)走,走到了f2,現(xiàn)在應(yīng)該是 : man1,2 microtask 1,2,3,f2, ... ... f1
  5. 因為f2中有3個then,所以現(xiàn)在應(yīng)該是 : man1,2 microtask 1,2,3,f2, f2.1, f2.2, f2.3 ... ... f1
  6. 到這里應(yīng)該沒有疑問吧. 有人說f2.2中有mic4,為啥不走他呢,因為根原則 mic4會在 f2執(zhí)行完畢之后執(zhí)行,所以現(xiàn)在應(yīng)該是 : man1,2 microtask 1,2,3,f2, f2.1, f2.2, f2.3, mic4, ... ... f1
  7. 繼續(xù)走到f3 ,會輸出f3,但是在then中新建了一個future,所以他會放到event隊列的最后執(zhí)行,所以這個時候應(yīng)該是 輸出: man1,2 microtask 1,2,3,f2, f2.1, f2.2, f2.3, mic4,f3 ... ... f3.1, f3.2, f1
  8. 繼續(xù)走到了的 f4, 整個也就執(zhí)行結(jié)束了.最后的結(jié)果是 : man1,2 microtask 1,2,3,f2, f2.1, f2.2, f2.3, mic4,f3 ,f4 ,f3.1, f3.2, f1

看是上面的,我們知道Dart中,的事件順序是 main->microtask->event,如果中間穿插,也是規(guī)則的總結(jié)如下:

  1. Future 的執(zhí)行順序為Future的在 EventQueue 的排列順序(****main->****microtask->event****)
  2. 當任務(wù)需要延遲執(zhí)行時,可以使用 new Future.delay() 來將任務(wù)延遲執(zhí)行。
  3. Future 如果執(zhí)行完才添加 than ,該任務(wù)會被放入 microTask,當前 Future 執(zhí)行完會執(zhí)行 microTask,microTask 為空后才會執(zhí)行下一個Future。(上面的第6步)
  4. Future 是鏈式調(diào)用,意味著Future 的 then 未執(zhí)行完,下一個then 不會執(zhí)行。

重要要在腦海里有一個 EventQueue 的隊列模型,牢記先進先出。

FutureBuilder

FutureBuilder是一個將異步操作和異步UI更新結(jié)合在一起的類,通過它我們可以將網(wǎng)絡(luò)請求,數(shù)據(jù)庫讀取等的結(jié)果更新的頁面上。

構(gòu)造方法:

const FutureBuilder({
    Key key,
    this.future,           //Future對象表示此構(gòu)建器當前連接的異步計算;
    this.initialData,      //表示一個非空的Future完成前的初始化數(shù)據(jù);
    @required this.builder,// AsyncWidgetBuilder類型的回到函數(shù),是一個基于異步交互構(gòu)建widget的函數(shù);
  }) 

這個builder函數(shù)接受兩個參數(shù)BuildContext contextAsyncSnapshot<T> snapshot,它返回一個widget。AsyncSnapshot包含異步計算的信息,它具有以下屬性:

connectionState - 枚舉ConnectionState的值,表示與異步計算的連接狀態(tài),ConnectionState有四個值:none,waiting,active和done;

data - 異步計算接收的最新數(shù)據(jù);

error - 異步計算接收的最新錯誤對象;

AsyncSnapshot還具有hasDatahasError屬性,以分別檢查它是否包含非空數(shù)據(jù)值或錯誤值。

例子

官方demo

FutureBuilder<String>(
  future: _calculation, // a previously-obtained Future<String> or null
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.none:
        return Text('Press button to start.');
      case ConnectionState.active:
      case ConnectionState.waiting:
        return Text('Awaiting result...');
      case ConnectionState.done:
        if (snapshot.hasError)
          return Text('Error: ${snapshot.error}');
        return Text('Result: ${snapshot.data}');
    }
    return null; // unreachable
  },
)

可以看到 FutureBuilder 定義了一個泛型,這個泛型是用來獲取快照中數(shù)據(jù)時用的。

我們再來看一下 snapshot.connectionState 都有哪些值:

<colgroup><col span="1" width="360"><col span="1" width="360"></colgroup>

ConnectionState 當前沒有連接到任何的異步任務(wù)
ConnectionState.none 當前沒有連接到任何的異步任務(wù)
ConnectionState.waiting 連接到異步任務(wù)并等待進行交互
ConnectionState.active 連接到異步任務(wù)并開始交互
ConnectionState.done 異步任務(wù)中止

現(xiàn)在了解了之后我們在打開一個頁面的時候肯定會有網(wǎng)絡(luò)請求,這個時候要顯示 loading 之類的,我們就可以利用當前快照的狀態(tài)來返回不同的 widget

首先看build代碼:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('FutureBuilderPage'),
    ),
    body: FutureBuilder(
      builder: (context, snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
          case ConnectionState.active:
          case ConnectionState.waiting:
            print('waiting');
            return Center(child: CupertinoActivityIndicator());
          case ConnectionState.done:
            print('done');
            if (snapshot.hasError) {
              return Center(
                child: Text('網(wǎng)絡(luò)請求出錯'),
              );
            }
            return generateListView();
        }
        return null;
      },
      future: _future,
    ),
  );
}

Scaffold 的 body 直接返回一個 FutureBuilder,根據(jù)不同狀態(tài)來返回了不同的 widget。

這里需要注意的一點是:我們知道 StatefulWidget會長時間維護一個 State,當有變動的時候會調(diào)用 didUpdateWidget 方法,就要重新build了。所以 FutureBuilder 的官方文檔上有這么一段文字:

The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig , or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.buildmethod call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.A general guideline is to assume that every build method could get called every frame, and to treat omitted calls as an optimization.

大致意思就是說 future 這個參數(shù)建議在 initState() 里初始化,不要在 build 方法里初始化,這樣的話會一直 rebuild。

為什么呢,我們查看 didUpdateWidget 源碼:

@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (oldWidget.future != widget.future) {
    if (_activeCallbackIdentity != null) {
      _unsubscribe();
      _snapshot = _snapshot.inState(ConnectionState.none);
    }
    _subscribe();
  }
}

可以看出來這里是判斷了 future 這個字段, 所以我們一定不要在 build 方法里初始化 future 參數(shù)!

所以,我們在 initState()方法里初始化:

Future _future;
Dio _dio;
int date = 20190523;
List<Stories> _newsData = [];
@override
void initState() {
  super.initState();
  _dio = Dio();
  _future = getNewsList();
}
// 獲取知乎每天的新聞,數(shù)據(jù)獲取成功后 setState來刷新數(shù)據(jù)
Future getNewsList() async {
  var response =
    await _dio.get('https://news-at.zhihu.com/api/4/news/before/$date');
  setState(() {
    _newsData.addAll(ZhiHuNews.fromJson(response.data)._stories);
  });
}

FutureBuiler先說這點,以后大量使用后再做詳細記錄 ~

參考:

https://segmentfault.com/a/1190000008800122

http://www.itdecent.cn/p/c0e30769ea7e

最后編輯于
?著作權(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ù)。

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