Dart 异步
dart提供了Future 和Stream 对象来实现异步方案
future
Future 是一个异步执行并且在未来的某一个时刻完成(或失败)的任务。
- 这意味着其结果将在未来某一个时刻准备好
当实例化一个Future时:
- 该 Future 的一个实例被创建并记录在由 Dart 管理的内部数组中;
- 需要由此 Future 执行的代码直接推送到 Event 队列中去;
- 该 future 实例 返回一个状态(= incomplete);
- 如果任务正在event队列中等待,返回一个未完成的
Future
- 如果任务执行结束,返回完成状态的
Future
,此时可以获得该任务结果- 如果存在下一个同步代码,执行它(非 Future 的执行代码)
如何创建Future
通过Future的API
-
Future(FutureOr<T> computation())
-
computation返回值类型可以是任意类型 但是接收者类型一定要对应上
-
Future<String> f=Future(()=>"ok"); Future<int> fint=Future(()=> 12); Future<Future> future=Future(()=> f);
-
需要注意的是computation中的内容是异步执行的
-
-
Future.sync(FutureOr<T> computation())
- 同步方法 computation()里面的内容会立即执行
-
Future.value([FutureOr<T> value])
- 创建一个future对象,以value为结果完成
-
Future.delayed(Duration duration, [FutureOr<T> computation()])
- 创建一个延时执行computation()内容的future对象
-
Future.error(Object error, [StackTrace stackTrace])
- 创建一个future 以error状态完成
-
Future.microtask(FutureOr<T> computation())
- 创建future对象并将computation 任务放到microtask队列
如何处理Future呢
通过FutureApi
常用API 如下
-
Future.then()
我们可以通过then()
方法获得Future内任务执行结束的结果-
Future.value(18).then((data)=>print(data)); // 输出 18
-
通过
then
链式调用-
Future.value("start").then((data) { print(data); return data; }).then((data) { print("request= $data"); return "end"; }).then((s) => print(s)); // 输出 //start //request= start //end
-
-
-
Future.catchError()
通过catchError()
来捕获任务执行产生的异常-
Future.error("oh create error").then((data) => print(data)).catchError((e) => print("捕捉到 error = "+e)); // 输出 捕捉到 error = oh create error
-
-
Future.whenComplete()
通过whenComplete()
方法来执行无论任务执行成功还是失败都需要执行的操作 比如资源释放-
Future.error("oh create error") .then((data) => print(data)) .catchError((e) => print("捕捉到 error = " + e)) .whenComplete(() => print("需要将资源释放")); //输出 //捕捉到 error = oh create error //需要将资源释放
-
-
通过
Future.wait()
可以等待多个异步任务执行完成后,再调用 then().-
Future.wait([ Future(() => "123"), Future(() => 11), Future(() => "end"), ]).then((listValue) { listValue.forEach((f) => print(f)); }); // 注意一定是顺序创建顺序输出 future将事件放到了event队列 这个对垒又是先进先出的 所以执行顺序跟future的创建顺序一致 // 123 // 11 // end
-
其中任意一个任务产生err 都将进入
catchError
-
Future.wait([ Future(() => "123"), Future(() => 11), Future(() => "end"), Future.error("create err") ]).then((listValue) { listValue.forEach((f) => print(f)); }).catchError((e) => print("error: $e")); // 输出 error: create err
-
-
当然这只是几个常用api 还有很多不一一列举
通过async & await
dart 1.9 之后提供了async 和await 关键字 来简化处理future,可以让代码更像像同步写法,不需要调用future的api也可以处理Future
在这应该先看下面的await 在看上面的async 因为处理future吗 是用的await 只是await 必须在async之内所以上面说了下async
通过async
声明异步函数
-
Future<String> com() async => "string"; Future<void> c() async=>print("object"); //如果函数没有返回值要声明为Future<void>
-
当使用async作为方法声明的后缀,表示
- 该方法返回一个Future
- 方法内部有可能执行异步耗时操作,它同步执行该方法内的代码直到第一个await关键字,然后它暂停该方法其他部分的执行
- 当await关键字引用的Future执行完成,该方法内的下一行代码立即执行
通过await 等待Future执行结束处理返回结果
-
await
表示等待后面的future 任务执行结束 -
我们可以通过
await
来等待future任务执行结束获取返回的值-
var data = await Future.value(18); print(data);// 输出 18
-
-
通过try catch finally 处理 等待执行产生的异常,以及最终处理
-
try { var a = await Future.error("oh create Error"); } catch (e) { print(e); } finally { print("关闭资源"); } // oh create Error // 关闭资源
-
重点注意
-
await 关键字必须在 被async 关键字标记的方法内使用
-
main() async { var data = await Future.value(18); // async关键字内可以有多个await var data1 = await Future.value(181); }
-
-
await 关键字阻塞的只是当前async方法的执行流程,并不是整个程序的执行流程
-
为了体现上述观点观察下面代码输出
-
main(){ print("main开始执行"); A().then((data)=>print(data)); print("main结束执行"); } Future<String> A() async { print("准备执行A"); await Future.delayed(Duration(seconds: 4), () => print("开始执行A")); print("A 执行中 "); return "A 执行结束"; } /* main开始执行 首先输出了 ”main开始执行“ 准备执行A 调用了异步函数A 同步执行第一个await之前的代码输出”准备执行A“ 执行到await中断 了A函数的执行 main结束执行 但是并没有中断main函数执行 继续输出 "main执行结束" 开始执行A 此时4秒过去了 延迟的future任务开始执行 并输出了 ”开始执行A“ 然任务执行结束 A 执行中 开始执行await 操作之后的代码 输出了 "A 执行中" A 执行结束 然后返回 此时整个A 执行结束 通过then获取到执行结果并输出 输出了“A 执行结束” */
-
-
如果你想要最后输出 ”main结束执行“
-
只要将 A函数等待执行即可
-
main() async { print("main开始执行"); // A().then((data)=>print(data)); print(await A()); print("main结束执行"); } Future<String> A() async { print("准备执行A"); await Future.delayed(Duration(seconds: 4), () => print("开始执行A")); print("A 执行中 "); return "A 执行结束"; } /* main开始执行 首先输出 “main开始执行” 准备执行A 接这执行到了await 等待A执行结束 开始执行A 依次输出了 “开始执行A”, A 执行中 “A 执行中“, A 执行结束 此时await 标记的任务执行结束并在main中输出其返回值 ”A 执行结束” main结束执行 紧接着执行await 之后的下一行代码 */
-
-
由此可见await 是要等待后面的future任务执行结束 才开始执行该异步函数内接下来的代码
- await只会阻塞当前异步函数
-
-
await 后面表达式的值通常是一个future对象
-
await Future.delayed(Duration(seconds: 4), () => print("开始执行A"));
-
如果表达不是future对象呢?
- 我的as 编译器提示(Await only futures)并给出移除await关键字的提示操作**,**并没有报错
- 文档这么说
- 在
await *表达式*
中,*表达式*
的值通常是一个 Future 对象; 如果不是,表达式的值会被自动包装成一个 Future 对象
- 在
- 我是这么理解
- 也就是说 表达式执行依然同步只有值被包装成了Future对象 表达式执行依然同步阻塞
-
下面举例子说明上面的两种情况
-
场景 耗时操作
taking
4秒返回string字符串 -
预期: 先输出 main开始执行, 输出 main执行结束,等待test执行结束后输出test 执行结果
-
先看情况二 await 表达式不是Future
-
main() { print("main开始执行 ${DateTime.now()}"); test().then((data) => print("$data,${DateTime.now()}")); print("main结束执行 ${DateTime.now()} "); } Future<String> test() async { return await taking(); } // 模拟耗时操作 4秒后返回一个 string 字符串 String taking() { sleep(Duration(seconds: 4)); return "taking 执行结束"; } /// main开始执行 2020-05-27 02:54:20.729637 /// main结束执行 2020-05-27 02:54:24.783372 /// taking 执行结束,2020-05-27 02:54:24.793373
- 分析结果
- 首先输出“main开始执行”没问题,乍一看test函数放到队列接着输出“main”执行结束,mian没其他代码执行eventloop开始处理event队列事件,通过then得到test函数的结果并输出 “taking 执行结束” 似乎也没什么问题。。然而真的是这样吗?
- 注意看输出 ”main 开始和结束“的时间差是4秒,
- test 函数中 taking 执行时间也是4秒
- 问题来了输出时间相差4秒说明main被中断了4秒,(await不是只是阻塞当前任务吗那main为什么也中断了4秒),既然中断了main为什么又是先输出的是” main 结束“而不是test的执行结果呢?
- 文档描述
若不是future表达式 则将表达式的结果包装成一个future
, - 注意这里是结果包装成future,表达式的执行过程仍然是同步的,只是执行结束之后结果被包装成了future并丢到event队列等待调用所以这里表达式同步执行4秒后结束首先输出了 main执行结束,此时main中又没有其他代码执行eventloop才处理evnet队列中的事件,通过调用then将结果同步返回,所以最后输出 test的执行结果
- 文档描述
-
-
再看情况一 await 后面是 future表达式
-
main() { print("main开始执行 ${DateTime.now()}"); testFuture().then((data) => print("$data,${DateTime.now()}")); print("main结束执行 ${DateTime.now()} "); } Future<String> testFuture() async { return await Future(() { return taking(); }); } // 模拟耗时操作 4秒后返回一个 string 字符串 String taking() { sleep(Duration(seconds: 4)); return "taking 执行结束"; } // 输出结果 //main开始执行 2020-05-27 13:58:31.511747 //main结束执行 2020-05-27 13:58:31.558949 //taking 执行结束,2020-05-27 13:58:35.605949
-
-
分析结果
-
与预期符合
-
-
通过对比可以证明上述我理解应该是没问题的 总结如下
- 当异步函数内await后的表达式是Future 那么对于当前异步函数是阻塞的对于当前异步函数的调用者则是异步的
- 当异步函数内await 后的表达式不是Future 那么该表达式的执行过程无论对于当前异步函数和调用者都是同步的会阻塞调用者,只是 表达式的结果对于调用者是异步的。
- async修饰的函数不一定就是异步执行的 真正执行异步的还是future await只是等地future执行结束
-
-
消除地狱回调
前面说过 async 内可以使用多个await
场景 登陆后获取用户信息后在根据信息获取数据
-
三个future
-
Future<String> login(String user) { return Future(() => "$user"); } Future<String> getUserInfo(String user) { return Future(() => "$user,info"); } Future<String> getData(String userinfo) { return Future(() => "$userinfo,data"); }
-
-
地狱回调
-
login("jack").then((user) { print("$user 登陆成功 获取用户信息"); getUserInfo(user).then((userInfo) { print("$userInfo 获取用户信息成功"); getData(userInfo).then((userdata) { print("获取 $userdata 数据成功"); }); }); });
-
-
then 链式调用
-
login("jack").then((user) { print("$user 登陆成功 获取用户信息"); return getUserInfo(user); }).then((userInfo) { print("$userInfo 获取用户信息成功"); return getData(userInfo); }).then((userdata) { print("获取 $userdata 数据成功"); });
-
-
async await 组合 类同步写法
-
var user = await login("jack"); print("$user 登陆成功 获取用户信息"); var info = await getUserInfo(user); print("$info 获取用户信息成功"); var data = await getData(info); print("获取 $data 数据成功");
-
-
三者输出结果相同 但是async和await 是最简洁的
总结
通过上面代码示例,可以总结如下几点包含对async和await的理解
-
async 修饰的函数叫异步函数只是说明该函数内可能有耗时操作内部操作并不一定都是异步
-
只有执行到async函数内的第一个await时该异步函数对于调用者才是异步的,在第一个await之前的代码与调用者同步执行
-
但是对于当前异步函数来说仍然是同步的因为await表示等待后面future执行结束, 只有await后的future 执行结束 该异步函数内await之后的代码才会继续执行
-
await 只是阻塞当前async标记的函数 如果后面是future 是不会中断调用任务的执行过程的
-
我都觉得啰嗦 只有第三条最重要 下面这个代码会说明一切
-
main() { print("A"); test().then((s) => print(s)); print("end"); } test() async { print("b"); await Future(() { print("c"); }); print("d"); return "e"; } // 输出 A b end c d e // 对比上面描述很好理解
-
-
-
-
await 只是等待 其后的future任务执行结束
-
async 和await 并没有执行异步操作 真是实现异步的还是future ,async 和await 只是一组可以省略调用futureApi的关键字,可以让程序看起来更简洁 参考上面回调地狱