Dart 中 Async 、 await 、Future 特性

在弄清Asyncawait之前,首先要清楚Dart是单线程模型,并不是靠子线程实现的异步操作。Asyncawait 实现的异步只适合耗时操作为等待类型的,例如接口请求,时间都耗费在等待上,等待期间,线程可以干别的事情。如果耗时操作是计算密集型,是不适用的,计算时会一直阻塞直到计算完毕。因为至始自终,都是单线程运行。这里涉及到Dart的事件循环模型:Event loops

让我们看一段示例:

代码很简单,_startMethod 间接调用了一个网络请求, 返回一串html。

import 'dart:io';
import 'package:http/http.dart' as http;

main() {
  _startMethod();
  print("C开始");
}


 Future _startMethod() async{
  print("A开始执行这个方法~");
  print(await getHttp());
  print("A执行结束");
}

Future<String> getHttp() async {
  final result = await http.get('https://www.jianshu.com/');
  return "请求到的数据:" +  result.body.toString();
}

执行结果 :接口等待期间,执行了main方法剩余的代码,打印了“C开始”,然后才打印接口返回数据。实现了异步处理效果。

I/flutter (22228): A开始执行这个方法~
I/flutter (22228): C开始
I/flutter (22228): 请求到的数据:<!DOCTYPE html>………………此处省略返回的HTML
I/flutter (22228): A执行结束

这里有两层 asyncawait 的组合,为了便于理解,我们先只看第一层组合即_startMethod方法。先不管 getHttp 里面的代码,只需要知道它发起了一个请求,返回值是请求的结果,这个方法是需要耗时等待的。

查阅官方文档得知,归纳出以下几点:

1. await 必须在async方法中使用。

444.PNG

2.async方法中在await前面的代码会立即同步执行,直到碰到await

222.PNG

3.await的作用是等待所标记的方法(即getHttp)获取返回结果。

333.PNG
await getHttp()//这个表达式就是返回的String: "请求到的数据:" +  result.body.toString()
//去掉await的话 , getHttp()是返回一个Future,不会在此等待,会立即执行后续的代码

代码跑到await getHttp()时,getHttp的代码是会执行(发起请求,但还没有获取到结果)。await后面的代码不会执行,直到getHttp返回结果才恢复执行。

4.当代码执行到await,会立即返回一个future

111.PNG

也就是说执行完getHttp后(尚未返回结果)会立马给_startMethod 方法返回一个future然后继续执行main剩下的代码,即print("C开始"); 。当请求结果返回时, await后面的代码才恢复执行,打印返回的http报文 和 print("A执行结束"),至此整个程序结束。
整个过程类似kotlin 协程的非阻塞挂起。

接下来再看getHttp方法,加上两处打印

Future<String> getHttp() async {
  print("B开始执行这个方法~");
  final result = await http.get('https://www.jianshu.com/');
  print("B  获取Http 结束~");
  return "请求到的数据:" +  result.body.toString();
}

执行的结果如下:

I/flutter (22228): A开始执行这个方法~
I/flutter (22228): B开始执行这个方法~
I/flutter (22228): C开始
I/flutter (22228): B  获取Http 结束~
I/flutter (22228): 请求到的数据:<!DOCTYPE html> ……此处省略HTML
I/flutter (22228): A执行结束

这里同理,会先执行 print("B开始执行这个方法~") , 运行await处时,会执行http.get方法,await后面的都不再执行。并立即返回一个Future给上一级。类似一个递归的思想,接口返回后,恢复await也是先恢复的内层的getHttp,再恢复外层的_startMethod。

拓展几个问题,以了解await运行的特性:

问题1:假如在main方法加入耗时操作: sleep(Duration(seconds: 10)),会发生什么呢?

main() {
  _startMethod();
  print("C开始");
  sleep(Duration(seconds: 10));
}

实验结果是打印"C开始"后,就阻塞了10秒,10秒后才打印出后续的http结果。这期间哪怕接口已经返回了数据也没有立即恢复执行await后面的代码,为什么呢?
dartEventLoop 事件模型可知,main方法执行完后才会从EventLoop持续获取事件并执行。接口的future有了返回值,只是发送了一个事件到EventLoop排队等候处理。由于此时线程并不空闲,正在执行10秒睡眠,并没有取出事件执行。直到main方法执行完处于空闲状态,才会取出该事件,打印http的返回结果。
由此可见,dart的await异步是单线程的,只有线程空闲了才会恢复执行await后面代码。

问题2:假如去掉main里的sleep,把 http.get方法换成sleep10秒操作,会发生什么呢?

Future<String> getHttp() async {
   print("B开始执行这个方法~");
   await sleep(Duration(seconds: 10));
   print("B  睡眠 结束~");
   return "请求到的数据:xxxxxx"  ;
}

结果如下:打印 “B开始执行这个方法” 后会等待10秒。等待期间并没有执行别的代码。

I/flutter (22228): A开始执行这个方法~
I/flutter (22228): B开始执行这个方法~  // 这里会等待10秒
I/flutter (22228): C开始
I/flutter (22228): B  获取Http 结束~
I/flutter (22228): 请求到的数据:xxxxxx
I/flutter (22228): A执行结束

这里的sleep是阻塞的,相当于计算密集型操作, 整个线程阻塞在这里, 等待期间main方法剩余的代码也不会执行,直到10秒结束。

问题3:假如去掉问题2中的await 关键字会如何?

Future<String> getHttp() async {
   print("B开始执行这个方法~");
   sleep(Duration(seconds: 3));
   print("B  睡眠 结束~");
   return "请求到的数据:xxxxxx"  ;
}

结果如下:“C开始”“B 获取Http 结束~” 换了位置。没有await, 整个getHttp()的代码都会顺序执行。也就是说await能改变代码执行顺序,因为执行到await会立即返回一个future,等待期间继续先执行外层的代码。

I/flutter (22228): A开始执行这个方法~
I/flutter (22228): B开始执行这个方法~  //等待10秒
I/flutter (22228): B  获取Http 结束~
I/flutter (22228): C开始
I/flutter (22228): 请求到的数据:xxxxxx
I/flutter (22228): A执行结束

关键点:

  1. Dartawait的异步是单线程的异步,执行到await时,await之前和await所标记的方法是会立即执行的(例如发起请求操作)。然后立马返回一个Future,继续执行外层剩余的代码。await后面的代码是不会执行的,只有future返回了结果才恢复执行await后面代码。这样就实现了异步的效果。

  2. 一组asyncawait 其实涉及到了 2个futureawait 关键字右边的方法就是一个futureasync方法返回值也一定是个futureawait标记的future执行完毕时,才继续执行await后面的代码。后面继续执行到return时,async方法返回的future才算执行完毕。

  3. 如果await 所标记的方法是等待类型的,那么等待期间可以继续运行外层的代码。

  4. 如果await所标记的方法是计算密集的,那么就会阻塞在这里,等计算完毕再运行外层代码,无法实现异步效果。

  5. await会改变执行顺序。因为await后面的代码已经作为一个event发送到了Event Loops中。会先执行外层剩余的main方法,再执行这个事件。至于是等待期间就执行main还是等待完才执行main,取决于上面的第3、4点

最后可以再试试这个例子: computesleep 的效果是一样的,会一直阻塞,而网络请求则可以在等待期间返回继续跑外层的代码。

Future<String> getHttp() async {
  print("B开始执行这个方法~");
//  final result = await http.get('https://www.jianshu.com/');
   await compute();
  print("B  获取Http 结束~");
  return "请求到的数据:";
}

Future<String> compute() async {
  int b = 0;
  for(int i= 0 ; i < 1000000000 ; i ++){
    b++;
  }
  return "compute数据:";
}

参考:

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

推荐阅读更多精彩内容