Flutter Future 详解

语雀

什么是 Future

FutureDart中提供的一个抽象类、泛型类,它用于封装一段在将来会被执行的代码逻辑。构造一个Future就会向event queue中添加一条记录。如果把event queue类比Android中的message queue的话,那么可以简单的把Future类比为Android中的Message.只不过Future中包含了需要完成的整个操作。并且利用Future的then和whenComplete方法可以指定在完成Future包含的操作后立马执行另一段逻辑。 关于Future的更多详情可以参阅官方文档

继续之前我感觉很有必要了解下Dart中的消息机制 !!!!

Dart的消息循环机制

Dart的单线程执行

当一个Dart的方法开始执行时,他会一直执行直至达到这个方法的退出点。换句话说Dart的方法是不会被其他Dart代码打断的。

Note:一个Dart的命令行应用可以通过创建isolates来达到并行运行的目的。isolates之间不会共享内存,它们就像几个运行在不同进程中的app,中能通过传递message来进行交流。出了明确指出运行在额外的isolates或者workers中的代码外,所有的应用代码都是运行在应用的main isolate中。要了解更多相关内容,可以查看https://www.dartlang.org/arti...

正如下图所示,当一个Dart应用开始的标志是它的main isolate执行了main方法。当main方法退出后,main isolate的线程就会去逐一处理消息队列中的消息。

image

isolate

Dart是基于单线程模型的语言。在Dart中有一个很重要的概念叫isolate,它其实就是一个线程或者进程的实现,具体取决于Dart的实现。默认情况下,我们用Dart写的应用都是运行在****main isolate****中的(可以对应理解为Android中的main thread)。当然我们在必要的时候也可以通过isolate API创建新的isolate,多个isolate可以更好的利用多核CPU的特性来提高效率。但是要注意的是在Dart中isolate之间是无法直接共享内存的不同的isolate之间只能通过isolate API进行通信因为 isolate 是单线程实体,所以 isolate中的代码是按顺序执行的。关于isolate更多详情可以参阅官方文档

Dart的消息循环和消息队列

一个Dart应用有一个消息循环(event loop) 和两个消息队列(event queue 和 microtask queue)

每个isolate都有单独的event loop

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

microtask 队列在Dart中是必要的,因为有时候事件处理想要在稍后完成一些任务但又希望是在执行下一个事件消息之前。

event队列包含Dart和来自系统其它位置的事件。但microtask队列只包含来自当前isolate的内部代码。

正如下面的流程图,当main方法退出后,event循环就开始它的工作。首先它会以FIFO的顺序执行micro task,当所有micro task执行完后它会从event 队列中取事件并执行。如此反复,直到两个队列都为空。

[图片上传失败...(image-da7b58-1590043915319)]

Dart 中的代码执行优先级可以分为三个级别:

  1. 在 Main 中写代码将最先执行;
  2. 执行完 Main 中的代码,然后会检查并执行 Microtask Queue 中的任务, 通常使用 scheduleMicrotask 将事件添加到 MicroTask Queue 中;
  3. 最后执行 EventQueue 队列中的代码,通常使用 Future 向 EventQueue加入事件,也可以使用 async 和 await 向 EventQueue 加入事件。

总结:Dart 中事件的执行顺序:Main > MicroTask > EventQueue。

异步任务调度

当有代码可以在后续任务执行的时候,有两种方式,通过dart:async这个Lib中的API即可:

  • 使用Future类,可以将任务加入到Event Queue的队尾
  • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾

当使用EventQueue时,需要考虑清楚,尽量避免microtask queue过于庞大,否则会阻塞其他事件的处理

image

大概了解上的知识点后,继续Future:

Future状态

  • pending - 执行中;
  • completed - 执行结束,分两种情况要么成功要么失败;

创建Future

常用的创建方法如下:

image.png

基本用法:

Future(FutureOr<T> computation())

Future 的构造方法,创建一个基本的Future

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

//2222
//1111

打印顺序是 2222-1111 这里准寻了Dart事件执行顺序 Main > MicroTask > EventQueue。

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

创建一个指定返回值的Future

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

Future.delayed()

创建一个延迟执行的 Future

//延迟三秒执行
 Future.delayed(Duration(seconds: 3), () {
   print("future delayed");
 });

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

根据某个集合,创建一系列的Future,并且会按顺序执行这些Future

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

//1
//2
//3

根据集合 1,2,3 创建三个延迟的Future,每两秒执行一次. 一共6秒

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

用来等待多个future完成,并收集它们的结果. 结果有成功和失败

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秒的例子对比下, 一个 顺序执行多个future,一个是异步执行多个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)

返回的是第一个执行完成的future的结果,不会管这个结果是正确的还是error的。

Future.doWhile()

类似java中doWhile的用法,重复性地执行某一个动作,直到返回false或者Future,退出循环。

使用场景:适用于一些需要递归操作的场景。

Future.microtask(FutureOr computation())

创建一个在microtask队列运行的future。

microtask队列的优先级是比event队列高的,而一般future是在event队列执行的,所以Future.microtask创建的future会优先于其他future进行执行。

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

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

处理Future结果

Flutter提供了下面三个方法,让我们来注册回调,来监听处理Future的结果。

//处理完成时候的回调,一般都是成功回调
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
//处理失败的回调,比如throw一个error就会走到这里
Future<T> catchError(Function onError, {bool test(Object error)});
//Future.whenComplete总是在Future完成后调用,不管Future的结果是正确的还是错误的。
Future<T> whenComplete(FutureOr action());

结合 async 和 await

Dart中他俩就是兄弟,一般都是一起出现的,来完成一个异步操作.

注意:await只能在async函数出现。 async函数,返回值是一个Future对象,如果没有返回Future对象,会自动将返回值包装成Future对象。 捕捉错误,一般是使用try/catch机制来做异常处理。 await 一个future,可以拿到Future的结果,直到拿到结果,才执行下一步的代码。

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方法中,定义了一个延迟操作,执行顺序按照dart事件执行顺序是 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捕获错误

执行顺序

Dart的事件顺序应该都明白了,加深下印象

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"));
}

结果:

flutter: 2

flutter: 1

flutter: 3

有疑问吗,应该没有疑问,因为f2是先初始化的.所以先执行的f2。可见 创建多个Future,执行顺序和和创建Future的先后顺序有关.和调用then的先后顺序无关

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"));
}

结果:

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

上面也是按照创建顺序执行的.

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');
}


结果:

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)

上面代码的执行顺序,是完全按照 Dart事件顺序去执行的.

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

main方法中的普通代码都是同步执行的,所以肯定是main打印先全部打印出来,等main方法结束后会开始检查microtask中是否有任务,若有则执行,执行完继续检查microtask,直到microtask列队为空。所以接着打印的应该是microtask的打印。最后会去执行event队列。由于有一个使用的delay方法,所以它的打印应该是在最后的。

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');

}

执行结果:
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. 首先执行两个main 1 和 main2 这个没有任何问题
  2. 继续执行 microtask1,2,3 这个也没有问题, 现在是 man1,2 microtask 1,2,3
  3. 走到delayed,现在应该是 : man1,2 microtask 1,2,3 ... ... f1
  4. 继续走,走到了f2,现在应该是 : man1,2 microtask 1,2,3,f2, ... ... f1
  5. 因为f2中有3个then,所以现在应该是 : man1,2 microtask 1,2,3,f2, f2.1, f2.2, f2.3 ... ... f1
  6. 到这里应该没有疑问吧. 有人说f2.2中有mic4,为啥不走他呢,因为根原则 mic4会在 f2执行完毕之后执行,所以现在应该是 : man1,2 microtask 1,2,3,f2, f2.1, f2.2, f2.3, mic4, ... ... f1
  7. 继续走到f3 ,会输出f3,但是在then中新建了一个future,所以他会放到event队列的最后执行,所以这个时候应该是 输出: man1,2 microtask 1,2,3,f2, f2.1, f2.2, f2.3, mic4,f3 ... ... f3.1, f3.2, f1
  8. 继续走到了的 f4, 整个也就执行结束了.最后的结果是 : 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,如果中间穿插,也是规则的总结如下:

  1. Future 的执行顺序为Future的在 EventQueue 的排列顺序(****main->****microtask->event****)
  2. 当任务需要延迟执行时,可以使用 new Future.delay() 来将任务延迟执行。
  3. Future 如果执行完才添加 than ,该任务会被放入 microTask,当前 Future 执行完会执行 microTask,microTask 为空后才会执行下一个Future。(上面的第6步)
  4. Future 是链式调用,意味着Future 的 then 未执行完,下一个then 不会执行。

重要要在脑海里有一个 EventQueue 的队列模型,牢记先进先出。

FutureBuilder

FutureBuilder是一个将异步操作和异步UI更新结合在一起的类,通过它我们可以将网络请求,数据库读取等的结果更新的页面上。

构造方法:

const FutureBuilder({
    Key key,
    this.future,           //Future对象表示此构建器当前连接的异步计算;
    this.initialData,      //表示一个非空的Future完成前的初始化数据;
    @required this.builder,// AsyncWidgetBuilder类型的回到函数,是一个基于异步交互构建widget的函数;
  }) 

这个builder函数接受两个参数BuildContext contextAsyncSnapshot<T> snapshot,它返回一个widget。AsyncSnapshot包含异步计算的信息,它具有以下属性:

connectionState - 枚举ConnectionState的值,表示与异步计算的连接状态,ConnectionState有四个值:none,waiting,active和done;

data - 异步计算接收的最新数据;

error - 异步计算接收的最新错误对象;

AsyncSnapshot还具有hasDatahasError属性,以分别检查它是否包含非空数据值或错误值。

例子

官方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 定义了一个泛型,这个泛型是用来获取快照中数据时用的。

我们再来看一下 snapshot.connectionState 都有哪些值:

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

ConnectionState 当前没有连接到任何的异步任务
ConnectionState.none 当前没有连接到任何的异步任务
ConnectionState.waiting 连接到异步任务并等待进行交互
ConnectionState.active 连接到异步任务并开始交互
ConnectionState.done 异步任务中止

现在了解了之后我们在打开一个页面的时候肯定会有网络请求,这个时候要显示 loading 之类的,我们就可以利用当前快照的状态来返回不同的 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('网络请求出错'),
              );
            }
            return generateListView();
        }
        return null;
      },
      future: _future,
    ),
  );
}

Scaffold 的 body 直接返回一个 FutureBuilder,根据不同状态来返回了不同的 widget。

这里需要注意的一点是:我们知道 StatefulWidget会长时间维护一个 State,当有变动的时候会调用 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 这个参数建议在 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 参数!

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

Future _future;
Dio _dio;
int date = 20190523;
List<Stories> _newsData = [];
@override
void initState() {
  super.initState();
  _dio = Dio();
  _future = getNewsList();
}
// 获取知乎每天的新闻,数据获取成功后 setState来刷新数据
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

https://www.jianshu.com/p/c0e30769ea7e

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342