Dart是单线程模型的语言,Dart原生通过Future、async和await支持异步编程模型,用法很简单,就是需要理解下为什么需要异步处理和Future的含义。
1.为什么需要异步处理?
前面提到Dart是单线程的,如果我们的程序存在耗时的操作,例如,请求api接口、文件IO等等,这些耗时操作都会阻塞线程,别的事情也干不了。如果在Flutter应用中阻塞线程就会出现不能绘制UI、点击事件没有响应、卡顿之类的情况。异步处理不会阻塞线程,其他任务可以继续运行。
2.Dart异步处理的用法
在Dart语言中有很多库的函数返回 Future 或者 Stream 对象,这些对象都是Dart对异步编程支持的实现。
- Future - 代表一个异步计算任务,可以获取任务的计算结果。
- Stream - 代表一个异步的数据序列,通常用于读取连续的数据或者事件。
2.1. Future
前面提到Future代表的是一个异步的计算任务,如果任务还没执行完成,我们是拿不到异步任务的结果。
例子:
我们先看一个http请求的例子
// 导入第三方http库
import 'package:http/http.dart' as http;
main() {
// 我们要请求的url
var url = "https://www.tizi365.com/";
// 调用get函数请求url, 返回一个封装了http请求任务的future对象
var fTask = http.get(url);
// 打印future对象, 看看返回什么东西
print(fTask);
// 向future对象注册回调函数,处理请求结果
fTask.then((response) {
// 异步计算任务完成,这里处理http请求结果。
// 打印http请求状态码
print('Response status: ${response.statusCode}');
});
// 打印main函数结束标记
print("main func end.");
}
http.get封装了一个请求url的异步任务,然后返回Future代表这个异步任务,这个时候任务还没执行,所以也拿不到结果。
执行程序输出如下
Instance of 'Future<Response>'
main func end.
Response status: 200
首先打印fTask,输出表示fTask是一个Future对象,将来会返回一个叫Response的结果对象。
重点来了,大家发现是先打印 main func end. ,而不是先输出http的请求状态码。
这是因为Dart在 main函数执行结束的时候才开始处理异步计算任务。
异步任务计算完成,才回调我们注册的回调函数,然后打印输出http请求状态码,所以最后才看到http请求的输出。
提示:如果要执行例子程序,需要下载http包,包的版本号: ^0.12.0+2 , 如何导入第三方包,请参考前面的教程。
2.2. await和async
前面例子在处理异步任务的时候,需要注册回调函数,才能处理异步任务,计算的结果,这种通过回调函数书写代码的方式可能性比较差,尤其是多层回调函数层层嵌套的时候。为了解决这个问题,Dart提供了await和async机制,让我们的异步任务代码,书写的时候看起来跟同步代码一样。
接上面http请求的例子:
import 'package:http/http.dart' as http;
// 使用 async关键词,标记main函数是一个异步函数,在函数体之前标记即可。
main() async {
// 我们要请求的url
var url = "https://www.tizi365.com/";
// 请求url, 通过await,等待future异步计算任务的结果,执行成功就直接返回结果
var response = await http.get(url);
// 打印http请求状态码
print('Response status: ${response.statusCode}');
print("main func end.");
}
执行程序输出:
Response status: 200
main func end.
输出结果的顺序,跟我们书写代码的顺序一致。
通过标记async和await关键词,我们的异步代码,看起来跟同步代码没什么区别。
- async关键词的作用就是标记一个函数是异步函数。
- await关键词的作用是等待异步任务的结果。
注意:await关键词只能在标记了async的异步函数中使用,否则报错。
2.2. Stream
Stream代表一个异步的数据序列,是一种异步读取流式数据的方式,例如我们要读取一个文件。
使用格式:
await for (数据类型 变量 in stream类型变量) {
// 处理数据
}
我们使用await标记for in循环语句,循环读取stream类型的变量中的数据,代码书写也很直观,跟同步代码的书写方式一致。