因为前面的HTTP请求用到了异步操做,很多小伙伴都被这个问题折了下腰,今天总结分享下实战成果。Dart是一个单线程的语言,遇到有延迟的运算(好比IO操做、延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感受到卡顿,因而一般用异步处理来解决这个问题。当遇到有须要延迟的运算(async)时,将其放入到延迟运算的队列(await)中去,把不须要延迟运算的部分先执行掉,最后再来处理延迟运算的部分。html
首先看一个案例:web
//HTTP的get请求返回值为Future<String>类型,即其返回值将来是一个String类型的值
getData() async { //async关键字声明该函数内部有代码须要延迟执行
return await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"}); //await关键字声明运算为延迟执行,而后return运算结果
}
复制代码
而后咱们调用这个函数,想获取其结果:json
String data = getData();
复制代码
在书写时,在IDE中这个代码是没有问题的,可是当咱们运行这段代码时,就报错了:segmentfault
为何呢?由于data
是String类型,而函数getData()
是一个异步操做函数,其返回值是一个await
延迟执行的结果。在Dart中,有await
标记的运算,其结果值都是一个Future
对象,Future
不是String类型,因此就报错了。后端
那若是这样的话,咱们就无法获取到延迟执行的结果了?固然能够,Dart规定有async
标记的函数,只能由await
来调用,好比这样:app
String data = await getData();
复制代码
可是要使用await
,必须在有async
标记的函数中运行,不然这个await
会报错: 异步
因而,咱们要为这个给data
赋值的语句加一个async
函数的包装:async
String data;
setData() async {
data = await getData(); //getData()延迟执行后赋值给data
}
复制代码
上面这种方法通常用于调用封装好的异步接口,好比
getData()
被封装到了其余dart文件,经过使用async
函数对其调取使用函数
再或者,咱们去掉async
函数的包装,在getData()
中直接完成data
变量的赋值:oop
String data;
getData() async {
data = await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"}); //延迟执行后赋值给data
}
复制代码
这样,data
就获取到HTTP请求的数据了。就这样就完了?是滴,只要记住两点:
await
关键字必须在async
函数内部使用async
函数必须使用await
关键字PS:
await
关键字真的很形象,等一等的意思,就是说,既然你运行的时候都要等一等,那我调用的时候也等一等吧
前面个讲到过,直接return await ...
的时候,实际上返回的是一个延迟计算的Future
对象,这个Future
对象是Dart内置的,有本身的队列策略,咱们就来聊聊这个Future。
先啰嗦一些关于Dart在线程方面的知识。
Dart是基于单线程模型的语言。在Dart也有本身的进程(或者叫线程)机制,名叫isolate。APP的启动入口main
函数就是一个isolate。玩家也能够经过引入import 'dart:isolate'
建立本身的isolate,对多核CPU的特性来讲,多个isolate能够显著提升运算效率,固然也要适当控制isolate的数量,不该滥用,不然走火入魔自废武功。有一个很重要的点,Dart中isolate之间没法直接共享内存,不一样的isolate之间只能经过isolate API进行通讯,固然本篇的重点在于Future
,不展开讲isolate,心急的小伙伴能够参考官方阅读理解或者参考大神tain335的人肉翻译。
Dart线程中有一个消息循环机制(event loop)和两个队列(event queue和microtask queue)。
event queue包含全部外来的事件:I/O,mouse events,drawing events,timers,isolate之间的message等。任意isolate中新增的event(I/O,mouse events,drawing events,timers,isolate的message)都会放入event queue中排队等待执行,比如机场的公共排队大厅。
microtask queue只在当前isolate的任务队列中排队,优先级高于event queue,比如机场里的某个VIP候机室,老是VIP用户先登机了,才开放公共排队入口。
若是在event中插入microtask,当前event执行完毕便可插队执行microtask。若是没有microtask,就没办法插队了,也就是说,microtask queue的存在为Dart提供了给任务队列插队的解决方案。
当main
方法执行完毕退出后,event loop就会以FIFO(先进先出)的顺序执行microtask,当全部microtask执行完后它会从event queue中取事件并执行。如此反复,直到两个队列都为空,以下流程图:
注意:当事件循环正在处理microtask的时候,event queue会被堵塞。这时候app就没法进行UI绘制,响应鼠标事件和I/O等事件。胡乱插队也是有代价的~
虽然你能够预测任务执行的顺序,但你没法准确的预测到事件循环什么时候会处理你指望的任务。例如当你建立一个延时1s的任务,但在排在你以前的任务结束前事件循环是不会处理这个延时任务的,也就是或任务执行多是大于1s的。
OK,了解以上信息以后,再来回到Future,小伙伴可能已经被绕晕了。
Future就是event,不少Flutter内置的组件好比前几篇用到的Http(http请求控件)的get
函数、RefreshIndicator(下拉手势刷新控件)的onRefresh
函数都是event。每个被await
标记的句柄也是一个event,每建立一个Future就会把这个Future扔进event queue中排队等候安检~
什么?那microtask呢?固然不会忘了这个,scheduleMicrotask,用法和Future基本同样。
前面讲到,用async
和await
组合,便可向event queue中插入event实现异步操做,好像Future的存在有些多余的感受,刚开始我本人也有这样的疑惑,且往下看。
当定义Flutter函数时,还能够指定其运行结果返回值的类型,以提升代码的可读性:
//定义了返回结果值为String类型
Future<String> getDatas(String category) async {
var request = await _httpClient.getUrl(Uri.parse(url));
var response = await request.close();
return await response.transform(utf8.decoder).join();
}
run() async{
int data = await getDatas('keji'); //由于类型不匹配,IDE会报错
}
复制代码
Future最主要的功能就是提供了链式调用。熟悉ES6语法的小伙伴乐开了花,链式调用解决两大问题:明确代码执行的依赖关系和实现异常捕获。WTF?还不明白?且看下面这些案例:
//案例1
funA() async{
...set an important variable...
}
funB() async{
await funA();
...use the important variable...
}
main() async {
funB();
}
//若是要想先执行funA再执行funB,必须在funB中await funA();
//funB的代码与funA耦合,未来若是funA废掉或者改动,funB中还须要通过修改以适配变动。
//案例2
funA() async{
try{
...set an important variable...
}catch(e){
do sth...
}finally{
do sth. else...
}
}
funB() async{
try{
...use the important variable...
}catch(e){
do sth...
}finally{
do sth. else...
}
}
main() async {
await funA();
await funB();
}
//没有明确体现出设置变量和使用变量之间的依赖关系,其余开发者难以理解你的代码逻辑,代码维护困难
//而且若是为了防止funA()或者funB()因发生异常致使程序崩溃
//要到funA()或者funB()中分别加入`try`、`catch`、`finally`
复制代码
为了解决上面的问题,Future提供了一套很是简洁的解决方案:
//案例3
funA(){
...set an important variable... //设置变量
}
funB(){
...use the important variable... //使用变量
}
main(){
new Future.then(funA()).then(funB()); // 明确表现出了后者依赖前者设置的变量值
new Future.then(funA()).then((_) {new Future(funB())}); //还能够这样用
//链式调用,捕获异常
new Future.then(funA(),onError: (e) { handleError(e); }).then(funB(),onError: (e) { handleError(e); })
}
复制代码
案例3的玩法是async
和await
没法企及的,所以掌握Future仍是颇有必要滴。固然了,Future的玩法不只仅局限于案例3,还有不少有趣的玩法,包括和microtask对象scheduleMicrotask配合使用,我这里就不一一介绍了,你们参考大神tain335的人肉翻译或者官网阅读理解吧。
Dart的isolate中加入了event queue和microtask queue后,有了一点协程的感受,或许这就是Flutter为啥在性能上敢和原生开发叫板的缘由之一吧。本篇的内容比较抽象,若是仍是有不明白的小伙伴,欢迎留言提问,我尽可能回答,哈哈哈,就酱,欢迎加入到Flutter圈子或flutter 中文社区(官方QQ群:338252156),群里有先后端及全栈各路大神镇场子,加入进来没事就写写APP挣点外快(这个真的有),顺便翻译翻译官方英文原稿拉一票粉丝,一举多得何乐而不为呢。