在弄清Async
和 await
之前,首先要清楚Dart是单线程模型,并不是靠子线程实现的异步操作。Async
和 await
实现的异步只适合耗时操作为等待类型的,例如接口请求,时间都耗费在等待上,等待期间,线程可以干别的事情。如果耗时操作是计算密集型,是不适用的,计算时会一直阻塞直到计算完毕。因为至始自终,都是单线程运行。这里涉及到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('//www.greatytc.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执行结束
这里有两层 async
和 await
的组合,为了便于理解,我们先只看第一层组合即_startMethod
方法。先不管 getHttp
里面的代码,只需要知道它发起了一个请求,返回值是请求的结果,这个方法是需要耗时等待的。
查阅官方文档得知,归纳出以下几点:
1. await
必须在async
方法中使用。
2.async
方法中在await
前面的代码会立即同步执行,直到碰到await
。
3.await
的作用是等待所标记的方法(即getHttp
)获取返回结果。
await getHttp()//这个表达式就是返回的String: "请求到的数据:" + result.body.toString()
//去掉await的话 , getHttp()是返回一个Future,不会在此等待,会立即执行后续的代码
代码跑到await getHttp()
时,getHttp
的代码是会执行(发起请求,但还没有获取到结果)。await
后面的代码不会执行,直到getHttp
返回结果才恢复执行。
4.当代码执行到await
,会立即返回一个future
。
也就是说执行完getHttp
后(尚未返回结果)会立马给_startMethod
方法返回一个future
。然后继续执行main
剩下的代码,即print("C开始")
; 。当请求结果返回时, await
后面的代码才恢复执行,打印返回的http报文 和 print("A执行结束")
,至此整个程序结束。
整个过程类似kotlin
协程的非阻塞挂起。
接下来再看getHttp
方法,加上两处打印
Future<String> getHttp() async {
print("B开始执行这个方法~");
final result = await http.get('//www.greatytc.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后面的代码,为什么呢?
由dart
的EventLoop
事件模型可知,main
方法执行完后才会从EventLoop
持续获取事件并执行。接口的future
有了返回值,只是发送了一个事件到EventLoop
排队等候处理。由于此时线程并不空闲,正在执行10秒睡眠,并没有取出事件执行。直到main
方法执行完处于空闲状态,才会取出该事件,打印http的返回结果。
由此可见,dart的await异步是单线程的,只有线程空闲了才会恢复执行await后面代码。
问题2:假如去掉main
里的sleep
,把 http.get
方法换成sleep
10秒操作,会发生什么呢?
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执行结束
关键点:
Dart
中await
的异步是单线程的异步,执行到await
时,await
之前和await
所标记的方法是会立即执行的(例如发起请求操作)。然后立马返回一个Future
,继续执行外层剩余的代码。await
后面的代码是不会执行的,只有future
返回了结果才恢复执行await
后面代码。这样就实现了异步的效果。一组
async
和await
其实涉及到了 2个future
,await
关键字右边的方法就是一个future
,asyn
c方法返回值也一定是个future
。await
标记的future
执行完毕时,才继续执行await
后面的代码。后面继续执行到return
时,async
方法返回的future
才算执行完毕。如果
await
所标记的方法是等待类型的,那么等待期间可以继续运行外层的代码。如果
await
所标记的方法是计算密集的,那么就会阻塞在这里,等计算完毕再运行外层代码,无法实现异步效果。await
会改变执行顺序。因为await
后面的代码已经作为一个event
发送到了Event Loops
中。会先执行外层剩余的main
方法,再执行这个事件。至于是等待期间就执行main还是等待完才执行main,取决于上面的第3、4点。
最后可以再试试这个例子: compute
和 sleep
的效果是一样的,会一直阻塞,而网络请求则可以在等待期间返回继续跑外层的代码。
Future<String> getHttp() async {
print("B开始执行这个方法~");
// final result = await http.get('//www.greatytc.com/');
await compute();
print("B 获取Http 结束~");
return "请求到的数据:";
}
Future<String> compute() async {
int b = 0;
for(int i= 0 ; i < 1000000000 ; i ++){
b++;
}
return "compute数据:";
}