推广

Flutter中的异步编程——Future

iseeyu2年前 (2024-02-21)推广151

简单总结一下,详细内容可以看文章The Event Loop and Dart

Dart中事件循环的一些主要概念:

  • Dart从两个队列执行任务:event事件队列和microtask微任务队列;
  • Dart的方法是不会被其他Dart代码打断的,当main执行完成后,main isolate的线程就会去逐一处理消息队列中的消息
  • 事件队列具有来自Dart(Future,Timer,Isolate Message等)和系统(用户输入,I/O等);
  • 微任务队列目前仅包含来自Dart;
  • 事件循环会优先处理微任务队列,microtask清空之后才将event事件队列中的下一个项目出队并处理。
  • 一旦两个队列都为空,则应用程序已完成工作,并且(取决于其嵌入程序)可以退出。
  • main()函数以及微任务和事件队列中的所有项目都在Dart应用程序的main isolate上运行。

什么是Future

Future<T>表示一个指定类型的异步操作结果(不需要结果可以使用Future<void>)当一个返回 future 对象的函数被调用时:

  1. 讲函数放入队列等待执行并返回一个未完成的Future对象

  2. 当函数操作执行完成,Future对象变为完成并携带一个值或一个错误
    上面两条分别对应两个状态:

  3. 运行状态(pending),表示任务还未完成,也没有返回值

  4. 完成状态(completed),表示任务已经结束(无论失败还是成功)

例如:

# demo1
main() {
  Future f1 = new Future(() {
    print("我是第一个");
  });
  f1.then((_) => print("f1 then"));
  print("我是main");
}
# print:
# 我是main
# 我是第一个
# f3 then

观察程序输出,首先执行完main函数然后再去执行任务栈中的内容,在该例中也就是我们使用Future假如到event任务栈中的任务<u>then中的方法会在Future处于完成态(completed)时立马执行</u>,之后我们再详细讲解。
Dart提供了数种创建Future的方法,其中最基本的为:

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

demo1中所使用的就是这种方式创建的Future。
其他创建Future的方式包括:

  • Future.value():返回一个指定值的Future
  • Future.delayed():返回一个延时执行的Future
main() {
  Future.delayed(Duration(milliseconds: 200),(){
    print("我是延迟的Future");
  });
  var future = Future.value("我是Future");
  future.then((value) => print(value));
}
# print:
# 我是Future
# 我是延迟的Future

这端代码执行了两个分支:

  • main()方法
  • event队列

Future中的任务调度

前面讲过:当Future执行完成后,then()注册的回调函数会立即执行,但是then中的函数并不会被添加到事件队列中,只是在事件队列中的任务被执行完成后才被立刻执行(可以理解为:将网络请求放在队列中进行执行,拿到结果后在then中刷新UI)。

main() {
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => {print("创建f3")});
  f3.then((value) => print("我是f3"));
  f2.then((value) => print("我是f2"));
  f1.then((value) => print("我是f1"));
}

上面程序的输出结果为:

我是f1
我是f2
创建f3
我是f3

首先,任务栈符合以FIFO的方式运行,f1,f2,f3一次被加入到任务栈,then()注册的函数并不会被添加到队列,也不会直接运行。当任务栈中任务被执行后,立刻运行then中的函数,依次类推。可以看到,then中的回调函数执行的顺序并不取决于注册的顺序,而仅仅与其Future被加入到任务栈的顺序有关。
注意:new Future(() => null)和new Future(null)有本质上的区别,一个函数体为空,什么都不做;一个是参数为空,不存在函数。
稍微修改一下上例中的代码:

main() {
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => {print("创建f3")});
  f3.then((_) => print("我是f3"));

  f2.then((_) {
    print("我是f2");
    new Future(() => print("我是一个新的"));
    f1.then((_) {
      print("我是f1");
    });
  }).then((value) => print("我还是f2"));
}

执行结果为:

我是f2
我还是f2
我是f1
创建f3
我是f3
我是一个新的

先看一下then的定义:

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

这里涉及到两个关键点:

  • 如果Future在then被调用之前已经完成,那么then中的函数会被作为任务添加到microtask队列中;
  • then会返回新的新的Future,并且该Future在onValue(then中注册的回调函数)或者onError被执行时就已经处于完成状态了。
  • 如果onValue(回调函数)返回值为一个Future,那么then返回的Future将会在onValue返回的future执行完成后处于完成状态
    关于后面两点:
main() async {
  Future f2 = new Future(() => null);

  f2.then((_) {
        print("我是真正的f2");
        Future f1 = new Future(() => null);
        f1.then((value) => print("我是f1"));
      })
      .then((value) => print(value))
      .then((value) => print("我还是f2吗"));
}

输出结果为:

我是真正的f2
我还是f2吗
我是f1

其中,每个then都会返回一个新的Future,而该future会在onValue,也就是回调函数执行时处于完成状态,然后立即执行该新future的回调函数。

稍微修改代码:
main() {
  Future f2 = new Future(() => null);

  f2.then((_) {
        print("我是真正的f2");
        Future f1 = new Future(() => null);
        f1.then((value) => print("我是f1"));
        return new Future(() => {print("全新的Future")});
      })
      .then((value) => print("我还是f2吗"))
      .then((value) => print("我不是了"));
}

运行结果为:

我是真正的f2
我是f1
全新的Future
我还是f2吗
我不是了

注意,then方法本身会返回一个future。在then中的函数也返回了一个Future,而then所返回的future会紧跟着函数返回的future之后处于完成状态再执行后续回调函数。

总结一下:

  • 当Future任务完成后,then()注册的回调函数会立即执行。需注意的是,then()注册的函数并不会添加到事件队列中,回调函数只是在事件循环中任务完成后被调用。
  • 如果Future在then()被调用之前已经完成计算,那么任务会被添加到微任务队列中,并且该任务会执行then()中注册的回调函数。
  • then会返回新的新的Future,并且该Future在onValue(then中注册的回调函数)或者onError被执行时就已经处于完成状态了。
  • 如果onValue(回调函数)返回值为一个Future,那么then返回的Future将会在onValue返回的future执行完成后处于完成状态

如何处理异步操作的结果

包括上面提到then,有三种方法处理Future的结果:

  • then: 处理操作执行结果或者错误并返回一个新的Future
  • catchError: 注册一个处理错误的回调
  • whenComplete:类似final,无论错误还是正确,Future执行结束后总是被调用

then中的onError只能处理当前Future中的错误,而catchError能处理整条调用链上的任何错误。

main() async {
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);

  f1
      .then((value) {
        return Future.error("错误了");
      })
      .then((value) => print("执行成功了吗"), onError: (error) => print(error))
      .then((value) => Future.error('错了!'))
      .catchError((error) => {print("我也发现:$error")});
  f2.then((_) {
    print("我是f2");
  }).whenComplete(() => print("完成了"));
}

输出结果为:

错误了
我也发现:错了!
我是f2
完成了

async和await

上面讲了Future的基本用法,以及使用Future API处理数据的方法。但是这种方法存在一个题:使用链式调用的方式把多个future连接在一起,会严重降低代码的可读性。
可以使用async和await关键字实现异步的功能。async和await可以帮助我们像写同步代码一样编写异步代码

main() async {
  Future f1 = new Future.delayed(Duration(milliseconds: 2000),() {
    return "我是第一个";
  });
  Future f2 = new Future(() {
    return "我是第二个";
  });
  f2.then((value) => print("哦哦哦"));
  print("开始了:${DateTime.now()}");
  print("${await f1}:${await f2}");
  print("结束了:${DateTime.now()}");
}

输出:

开始了:2020-10-13 15:37:16.871165
哦哦哦
我是第一个:我是第二个
结束了:2020-10-13 15:37:18.877511

注意:await只能在async函数里出现
要想改写异步代码,只需要在函数中添加async关键字

String getAString() {
  return "我是一个字符串";
}
## 改写为异步代码
Future<String> getAString() async{
  return "我是一个字符串";
}

需要注意的是,在普通函数中,return返回的为T,那么在async函数中返回的是Future<T>。但是并不需要显示的去指明返回的类型,Dart会自动将返回值包装成Future对象。但是,如果原函数返回的为Future<T>,在async函数中返回的仍然是是Future<T>。若async函数没有返回值,那么Dart会返回一个null值的Future。

main() {
  print("main函数开始了");
  firstString();
  secondString();
  thirdString();
  print("main函数结束了");
}

firstString() async{
  print("firstString函数开始了");
  Future.delayed(Duration(milliseconds: 300), () {
    return "我是一个字符串";
  }).then((value) => {print(value)});
  print("firstString函数结束了");
}

secondString() {
  print("我是二个字符串");
}

thirdString() {
  print("我是三个字符串");
}

上面代码的输出结果为:

main函数开始了
firstString函数开始了
firstString函数结束了
我是二个字符串
我是三个字符串
main函数结束了
我是一个字符串

注意观察代码的执行顺序,函数按照顺序执行,首先执行main函数,接着按照顺序执行firstString()、secondString()thirdString()。Future.delayed并不会阻碍任何代码的执行,这符合上文中讲的非阻塞调用,Future并不会阻塞它所在函数的执行。
我们稍微修改一下代码:

main() {
  print("main函数开始了");
  firstString();
  secondString();
  thirdString();
  print("main函数结束了");
}

firstString() async {
  print("firstString函数开始了");
  Future future = Future.delayed(Duration(milliseconds: 300), () {
    return "我是一个字符串";
  });
  print(await future);
  print("firstString函数结束了");
}

secondString() {
  print("我是二个字符串");
}

thirdString() {
  print("我是三个字符串");
}

输出结果为:

main函数开始了
firstString函数开始了
我是二个字符串
我是三个字符串
main函数结束了
我是一个字符串
firstString函数结束了

对比两次结果不难发现,async和await关键字使得原本非阻塞式的函数变的同步了,成了阻塞函数了。函数遇到Future,再其未执行完之前一直处于阻塞状态。但是main函数依旧正常执行,并不会被async函数所阻塞。async和await只会作用于当前函数,并不会对其他外部函数造成执行上的影响。
await也可以帮助我们在执行下个语句之前确保当前语句执行完毕:

main() async {
  print("main函数开始了:${DateTime.now()}");
  print(await firstString());
  print(await secondString());
  print(await thirdString());
  print("main函数结束了:${DateTime.now()}");
}

firstString() {
  return Future.delayed(Duration(milliseconds: 300), () {
    return "我是一个字符串";
  });
}

secondString() {
  return Future.delayed(Duration(milliseconds: 200), () {
    return "我是二个字符串";
  });
}

thirdString() {
  return Future.delayed(Duration(milliseconds: 100), () {
    return "我是三个字符串";
  });
}

输出结果为:

main函数开始了:2020-10-13 16:24:46.897353
我是一个字符串
我是二个字符串
我是三个字符串
main函数结束了:2020-10-13 16:24:47.527151

扫描二维码推送至手机访问。

版权声明:本文由西安泽虎代运营发布,如需转载请注明出处。

转载请注明出处https://www.0291.com.cn/post/57583.html

相关文章

数码产品为例如何跟京东淘宝抢流量 抢到手的就是自己的。

数码产品为例如何跟京东淘宝抢流量 抢到手的就是自己的。

前言: 喜欢一个人,能抢则抢,抢到手的就是自己的。 曾经我也喜欢过一个女孩,只不过不敢明说,她身边比我优秀的男孩不要太多,我算什么。 有人说:人性自私,果断挖。 我是一个不愿主动勾搭的人,脸皮薄也不敢抢,怕抢了失败会丢脸。 但事实上,当一个人什么都不跟别人抢,往往会失去很多机会...

细数App推广要做哪些事!懂渠道,做方案,有人脉,分析竞品……

细数App推广要做哪些事!懂渠道,做方案,有人脉,分析竞品……

1,学会 竞品分析 竞品分析是各个岗位上最重要技能之一。 只有 产品经理需要做竞品分析吗?在我来看,包括CEO在内的任何一个职位都是需要“竞品分析”,竞品分析可能不是一定要去分析竞品的某一个产品功能,而是站在不同角度去分析竞品的方方面面,前提是有价值的产品或者团队。产品经理...

新闻营销和软文营销的区别

随着互联网的发展,越来越多的企业或个人从线下到线上推广,快速塑造形象,为企业带来更多有价值的推广,然后企业在做线上品牌宣传推广,肯定少不了与新闻营销2个网络推广方法。那么,软文营销与新闻营销之间有何区别?企业应该如何选择合适的推广方式。下面,九州互营作为一家新闻营销、软文营...

营销与营销策略(如何制定有效的营销策略)

营销与营销策略(如何制定有效的营销策略)

是企业实现目标的重要手段,它是指企业通过制定相关的营销策略,以达到销量最大化、利润最大化、增加形象度、提高客户满意度等目的。本文来源明雪轩传媒营销平台,如需转载需获得授权。1. 营销的重要性营销是企业实现目标的重要手段,它对企业的利润、发展有着重要影响。营销不仅可以帮助企业...

千牛超级店长过期怎么办,千牛超级店长在哪里找(千牛初级版到期怎么办)

千牛超级店长过期怎么办,千牛超级店长在哪里找(千牛初级版到期怎么办)

首先是超级店长的折扣功能失效,其中有几方面原因,可能商家设置的折扣活动过期的,所以超级店长就自动失效的,也有可能是参加的活动到期了,如果商家需要继续折扣,可以重新设置优惠折扣,重新续费就行了。...

现在,非常期待与您的又一次邂逅

我们努力让每一部企业宣传片和抖音短视频成为商业大片