flutter 异步async、await和Future的使用技巧
由于前面的HTTP请求用到了异步操作,不少小伙伴都被这个问题折了下腰,今天总结分享下实战成果。Dart是一个单线程的语言,遇到有延迟的运算(比如IO操作、延时执行)时,线程中按顺序执行的运算就会阻塞,用户就会感觉到卡顿,于是通常用异步处理来解决这个问题。当遇到有需要延迟的运算(async)时,将其放入到延迟运算的队列(await)中去,把不需要延迟运算的部分先执行掉,最后再来处理延迟运算的部分。
async和await
首先看一个案例:
- new Future.delayed(const Duration(seconds: ),
- // () => Navigator.of(context).pop(Constants.baseActionList[item]));
然后我们调用这个函数,想获取其结果:
- String data = getData();
在书写时,在IDE中这个代码是没有问题的,但是当我们运行这段代码时,就报错了:
为什么呢?因为data
是String类型,而函数getData()
是一个异步操作函数,其返回值是一个await
延迟执行的结果。在Dart中,有await
标记的运算,其结果值都是一个Future
对象,Future
不是String类型,所以就报错了。
那如果这样的话,我们就没法获取到延迟执行的结果了?当然可以,Dart规定有async
标记的函数,只能由await
来调用,比如这样:
- String data = await getData();
但是要使用await
,必须在有async
标记的函数中运行,否则这个await
会报错:
于是,我们要为这个给data
赋值的语句加一个async
函数的包装:
- String data;
- setData() async {
- data = await getData(); //getData()延迟执行后赋值给data
- }
- 上面这种方法一般用于调用封装好的异步接口,比如getData()被封装到了其他dart文件,通过使用async函数对其调取使用
再或者,我们去掉async
函数的包装,在getData()
中直接完成data
变量的赋值:
- String data;
- getData() async {
- data = await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"}); //延迟执行后赋值给data
- }
这样,data
就获取到HTTP请求的数据了。就这样就完了?是滴,只要记住两点:
await
关键字必须在async
函数内部使用- 调用
async
函数必须使用await
关键字
- PS:await关键字真的很形象,等一等的意思,就是说,既然你运行的时候都要等一等,那我调用的时候也等一等吧
Future简单科普
前面个讲到过,直接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基本一样。
为什么要用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挣点外快(这个真的有),顺便翻译翻译官方英文原稿拉一票粉丝,一举多得何乐而不为呢。
flutter 异步async、await和Future的使用技巧的更多相关文章
- 异步async、await和Future的使用技巧
由于前面的HTTP请求用到了异步操作,不少小伙伴都被这个问题折了下腰,今天总结分享下实战成果.Dart是一个单线程的语言,遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞, ...
- Python 进阶 异步async/await
一,前言 本文将会讲述Python 3.5之后出现的async/await的使用方法,我从上看到一篇不错的博客,自己对其进行了梳理.该文章原地址https://www.cnblogs.com/dhcn ...
- JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧
原文地址:How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding wi ...
- 我也来说说C#中的异步:async/await
序 最近看了一些园友们写的有关于异步的文章,受益匪浅,写这篇文章的目的是想把自己之前看到的文章做一个总结,同时也希望通过更加通俗易懂的语言让大家了解"异步"编程. 1:什么是异步 ...
- 异步 async & await
1 什么是异步 异步的另外一种含义是计算机多线程的异步处理.与同步处理相对,异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程. 2 异步场景 l 不 ...
- 异步async/await简单应用与探究
感谢Marco CAO指出的两点错误,已做出修改与补充 异步函数(async/await)简单应用 .NET Framework4.5提供了针对异步函数语法糖,简化了编写异步函数的复杂度. 下面通过一 ...
- C#Framework4.0支持异步async/await语法
由于用户使用的是XP系统,但是程序里异步都是通过async/await代码来实现的,然而async/await需要Framework4.5版本才可以,而XP系统最高只能支持到Framework4.0, ...
- .Net Core异步async/await探索
走进.NetCore的异步编程 - 探索 async/await 前言: 这段时间开始用.netcore做公司项目,发现前辈搭的框架通篇运用了异步编程方式,也就是async/await方式,作为一个刚 ...
- 请教 C# 异步 async await 问题
各位园友,之前对C#异步只是肤浅了解,请教一个具体问题. 需求: 前台会发送一个Array,这个数组都是 id的集合,比较大.分两步,首先保存这些id,然后去调用异步方法. 可以正常返回json,也可 ...
随机推荐
- [LeetCode] 206. Reverse Linked List_Easy tag: Linked List
Reverse a singly linked list. Example: Input: 1->2->3->4->5->NULL Output: 5->4-> ...
- ASP.NET Core 1.0 基础与应用启动
.NET Core http://dotnet.github.io/[https://github.com/dotnet/coreclr] ASP.NET Core 1.0 https://get.a ...
- 关于SpringBoot中静态资源访问的问题
第一种方式 : 放在src/main/webapp目录下 第二种方式:放在classpath下(网页存放于static目录下, 默认的"/"指向的是~/resouces/stati ...
- Docker概念学习系列之Docker核心概念之仓库Repository
不多说,直接上干货! Docker仓库 仓库(Repository)是集中存放镜像的地方,分公共仓库和私有仓库. 仓库是集中存放镜像文件的场所. 有时候会把仓库和仓库注册服务器(Registry)混为 ...
- 【转】谷歌三大核心技术(二)Google MapReduce中文版
Google MapReduce中文版 译者: alex 摘要 MapReduce 是一个编程模型,也是一个处理和生成超大数据集的算法模型的相关实现.用户首先创建一个Map函数处理一个 ...
- 计数排序/Counting Sort
计数排序的算法思想: 对于每一个元素x,只要确定了元素x有多少个比它小的元素,那么就可以知道其最终的位置. 记输入数组为A[n],存放最后排序输出的数组为B[n],提供临时存储空间的中间数组记为C[k ...
- 图片按日期分类和查看程序(WPF开发)(附源码)
手机方便了我们的生活,可以随时随地拍摄.越来越多的图片堆砌在电脑里.看到杂乱无章的图片,实在感到头痛.手动整理太复杂.基于此,我写了一个小程序,可以将图片按日期整理和查看.按日期查看图片,回忆过去的点 ...
- Python3 中使用sys.argv详解
#/usr/bin/env python #coding:utf-8 import sys # print(sys.argv[1]) def readFile(filename): "&qu ...
- SQL-结构化查询语言(2)
使用explain查询select查询语句的执行计划 mysql> explain select * from student where Sname='金克斯'\G ************* ...
- 【转】CSS3 Box-sizing
box-sizing是CSS3的box属性之一.一说到CSS的盒模型(Box model)我想很多人都会比较烦,特别是对于新手,然而这个Box model又是我们CSS运用中比较重要的一个属性.那么C ...