Flutter Future 詳解

語雀

什么是 Future

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

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

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

Dart的單線程執(zhí)行

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

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

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

image

isolate

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

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

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

每個(gè)isolate都有單獨(dú)的event loop

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

microtask 隊(duì)列在Dart中是必要的,因?yàn)橛袝r(shí)候事件處理想要在稍后完成一些任務(wù)但又希望是在執(zhí)行下一個(gè)事件消息之前。

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

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

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

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

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

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

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

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

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

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

image

大概了解上的知識點(diǎn)后,繼續(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)建一個(gè)基本的Future

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

//2222
//1111

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

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

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

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

Future.delayed()

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

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

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

根據(jù)某個(gè)集合,創(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)建三個(gè)延遲的Future,每兩秒執(zhí)行一次. 一共6秒

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

用來等待多個(gè)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個(gè)延遲的例子,三秒之后輸出 [1,2,3]. 和上面6秒的例子對比下, 一個(gè) 順序執(zhí)行多個(gè)future,一個(gè)是異步執(zhí)行多個(gè)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)

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

Future.doWhile()

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

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

Future.microtask(FutureOr computation())

創(chuàng)建一個(gè)在microtask隊(duì)列運(yùn)行的future。

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

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

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

處理Future結(jié)果

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

//處理完成時(shí)候的回調(diào),一般都是成功回調(diào)
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
//處理失敗的回調(diào),比如throw一個(gè)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)的,來完成一個(gè)異步操作.

注意:await只能在async函數(shù)出現(xiàn)。 async函數(shù),返回值是一個(gè)Future對象,如果沒有返回Future對象,會自動將返回值包裝成Future對象。 捕捉錯誤,一般是使用try/catch機(jī)制來做異常處理。 await 一個(gè)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方法中,定義了一個(gè)延遲操作,執(zhí)行順序按照dart事件執(zhí)行順序是 t1-t2-t3-123,運(yùn)行代碼能看到猜想是對的.

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)該沒有疑問,因?yàn)閒2是先初始化的.所以先執(zhí)行的f2??梢?創(chuàng)建多個(gè)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隊(duì)列
  3. event隊(duì)列.

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

有點(diǎn)多,看著有點(diǎn)繞,我們來試著梳理一下:

  1. 首先執(zhí)行兩個(gè)main 1 和 main2 這個(gè)沒有任何問題
  2. 繼續(xù)執(zhí)行 microtask1,2,3 這個(gè)也沒有問題, 現(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. 因?yàn)閒2中有3個(gè)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,為啥不走他呢,因?yàn)楦瓌t 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中新建了一個(gè)future,所以他會放到event隊(duì)列的最后執(zhí)行,所以這個(gè)時(shí)候應(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, 整個(gè)也就執(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í)行順序?yàn)镕uture的在 EventQueue 的排列順序(****main->****microtask->event****)
  2. 當(dāng)任務(wù)需要延遲執(zhí)行時(shí),可以使用 new Future.delay() 來將任務(wù)延遲執(zhí)行。
  3. Future 如果執(zhí)行完才添加 than ,該任務(wù)會被放入 microTask,當(dāng)前 Future 執(zhí)行完會執(zhí)行 microTask,microTask 為空后才會執(zhí)行下一個(gè)Future。(上面的第6步)
  4. Future 是鏈?zhǔn)秸{(diào)用,意味著Future 的 then 未執(zhí)行完,下一個(gè)then 不會執(zhí)行。

重要要在腦海里有一個(gè) EventQueue 的隊(duì)列模型,牢記先進(jìn)先出。

FutureBuilder

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

構(gòu)造方法:

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

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

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

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

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

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 定義了一個(gè)泛型,這個(gè)泛型是用來獲取快照中數(shù)據(jù)時(shí)用的。

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

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

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

現(xiàn)在了解了之后我們在打開一個(gè)頁面的時(shí)候肯定會有網(wǎng)絡(luò)請求,這個(gè)時(shí)候要顯示 loading 之類的,我們就可以利用當(dāng)前快照的狀態(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 直接返回一個(gè) FutureBuilder,根據(jù)不同狀態(tài)來返回了不同的 widget。

這里需要注意的一點(diǎn)是:我們知道 StatefulWidget會長時(shí)間維護(hù)一個(gè) State,當(dāng)有變動的時(shí)候會調(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 這個(gè)參數(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 這個(gè)字段, 所以我們一定不要在 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先說這點(diǎn),以后大量使用后再做詳細(xì)記錄 ~

參考:

https://segmentfault.com/a/1190000008800122

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

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

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