专栏首页编程之路的专栏Dart 语言异步编程之Future

Dart 语言异步编程之Future

  • Dart 异步编程
    • Dart 的事件循环
    • 调度任务
    • 延时任务
    • Future 详解
      • 创建 Future
      • 注册回调
    • async 和 await

Dart 异步编程

编程中的代码执行,通常分为同步异步两种。简单说,同步就是按照代码的编写顺序,从上到下依次执行,这也是最简单的我们最常接触的一种形式。但是同步代码的缺点也显而易见,如果其中某一行或几行代码非常耗时,那么就会阻塞,使得后面的代码不能被立刻执行。

异步的出现正是为了解决这种问题,它可以使某部分耗时代码不在当前这条执行线路上立刻执行,那究竟怎么执行呢?最常见的一种方案是使用多线程,也就相当于开辟另一条执行线,然后让耗时代码在另一条执行线上运行,这样两条执行线并列,耗时代码自然也就不能阻塞主执行线上的代码了。

多线程虽然好用,但是在大量并发时,仍然存在两个较大的缺陷,一个是开辟线程比较耗费资源,线程开多了机器吃不消,另一个则是线程的锁问题,多个线程操作共享内存时需要加锁,复杂情况下的锁竞争不仅会降低性能,还可能造成死锁。因此又出现了基于事件的异步模型。简单说就是在某个单线程中存在一个事件循环和一个事件队列,事件循环不断的从事件队列中取出事件来执行,这里的事件就好比是一段代码,每当遇到耗时的事件时,事件循环不会停下来等待结果,它会跳过耗时事件,继续执行其后的事件。当不耗时的事件都完成了,再来查看耗时事件的结果。因此,耗时事件不会阻塞整个事件循环,这让它后面的事件也会有机会得到执行。

我们很容易发现,这种基于事件的异步模型,只适合I/O密集型的耗时操作,因为I/O耗时操作,往往是把时间浪费在等待对方传送数据或者返回结果,因此这种异步模型往往用于网络服务器并发。如果是计算密集型的操作,则应当尽可能利用处理器的多核,实现并行计算。

在这里插入图片描述

Dart 的事件循环

Dart 是事件驱动的体系结构,该结构基于具有单个事件循环和两个队列的单线程执行模型。Dart虽然提供调用堆栈。但是它使用事件在生产者和消费者之间传输上下文。事件循环由单个线程支持,因此根本不需要同步和锁定。

Dart 的两个队列分别是

  • MicroTask queue 微任务队列
  • Event queue 事件队列

在这里插入图片描述

Dart事件循环执行如上图所示

  1. 先查看MicroTask队列是否为空,不是则先执行MicroTask队列
  2. 一个MicroTask执行完后,检查有没有下一个MicroTask,直到MicroTask队列为空,才去执行Event队列
  3. Evnet 队列取出一个事件处理完后,再次返回第一步,去检查MicroTask队列是否为空

我们可以看出,将任务加入到MicroTask中可以被尽快执行,但也需要注意,当事件循环在处理MicroTask队列时,Event队列会被卡住,应用程序无法处理鼠标单击、I/O消息等等事件。

调度任务

注意,以下调用的方法,都定义在dart:async库中。

将任务添加到MicroTask队列有两种方法

import  'dart:async';

void  myTask(){
    print("this is my task");
}

void  main() {
    // 1. 使用 scheduleMicrotask 方法添加
    scheduleMicrotask(myTask);

    // 2. 使用Future对象添加
    new  Future.microtask(myTask);
}

将任务添加到Event队列

import  'dart:async';

void  myTask(){
    print("this is my task");
}

void  main() {
    new  Future(myTask);
}

现在学会了调度任务,赶紧编写代码验证以上的结论

import  'dart:async';

void  main() {
    print("main start");

    new  Future((){
        print("this is my task");
    });

    new  Future.microtask((){
        print("this is microtask");
    });

    print("main stop");
}

运行结果:

main start
main stop
this is microtask
this is my task

可以看到,代码的运行顺序并不是按照我们的编写顺序来的,将任务添加到队列并不等于立刻执行,它们是异步执行的,当前main方法中的代码执行完之后,才会去执行队列中的任务,且MicroTask队列运行在Event队列之前。

延时任务

如需要将任务延伸执行,则可使用Future.delayed方法

new  Future.delayed(new  Duration(seconds:1),(){
    print('task delayed');
});

表示在延迟时间到了之后将任务加入到Event队列。需要注意的是,这并不是准确的,万一前面有很耗时的任务,那么你的延迟任务不一定能准时运行。

import  'dart:async';
import  'dart:io';

void  main() {
    print("main start");

    new Future.delayed(new  Duration(seconds:1),(){
        print('task delayed');
    });

    new Future((){
        // 模拟耗时5秒
        sleep(Duration(seconds:5));
        print("5s task");
    });

    print("main stop");
}

运行结果:

main start
main stop
5s task
task delayed

从结果可以看出,delayed方法调用在前面,但是它显然并未直接将任务加入Event队列,而是需要等待1秒之后才会去将任务加入,但在这1秒之间,后面的new Future代码直接将一个耗时任务加入到了Event队列,这就直接导致写在前面的delayed任务在1秒后只能被加入到耗时任务之后,只有当前面耗时任务完成后,它才有机会得到执行。这种机制使得延迟任务变得不太可靠,你无法确定延迟任务到底在延迟多久之后被执行。

Future 详解

Future类是对未来结果的一个代理,它返回的并不是被调用的任务的返回值。

void  myTask(){
    print("this is my task");
}

void  main() {
    Future fut = new  Future(myTask);
}

如上代码,Future类实例fut并不是函数myTask的返回值,它只是代理了myTask函数,封装了该任务的执行状态。

创建 Future

Future的几种创建方法

  • Future()
  • Future.microtask()
  • Future.sync()
  • Future.value()
  • Future.delayed()
  • Future.error()

其中sync是同步方法,任务会被立即执行

import  'dart:async';

void  main() {
    print("main start");

new  Future.sync((){
    print("sync task");
});

new  Future((){
    print("async task");
});

    print("main stop");
}

运行结果:

main start
sync task
main stop
async task

注册回调

Future中的任务完成后,我们往往需要一个回调,这个回调立即执行,不会被添加到事件队列。

import 'dart:async';

void main() {
  print("main start");


  Future fut =new Future.value(18);
  // 使用then注册回调
  fut.then((res){
    print(res);
  });

 // 链式调用,可以跟多个then,注册多个回调
  new Future((){
    print("async task");
  }).then((res){
    print("async task complete");
  }).then((res){
    print("async task after");
  });

  print("main stop");
}

运行结果:

main start
main stop
18
async task
async task complete
async task after

除了then方法,还可以使用catchError来处理异常,如下

  new Future((){
    print("async task");
  }).then((res){
    print("async task complete");
  }).catchError((e){
    print(e);
  });

还可以使用静态方法wait 等待多个任务全部完成后回调。

import 'dart:async';

void main() {
  print("main start");

  Future task1 = new Future((){
    print("task 1");
    return 1;
  });

  Future task2 = new Future((){
    print("task 2");
    return 2;
  });
    
  Future task3 = new Future((){
    print("task 3");
    return 3;
  });

  Future fut = Future.wait([task1, task2, task3]);
  fut.then((responses){
    print(responses);
  });

  print("main stop");
}

运行结果:

main start
main stop
task 1
task 2
task 3
[1, 2, 3]

如上,wait返回一个新的Future,当添加的所有Future完成时,在新的Future注册的回调将被执行。

async 和 await

在Dart1.9中加入了asyncawait关键字,有了这两个关键字,我们可以更简洁的编写异步代码,而不需要调用Future相关的API

async 关键字作为方法声明的后缀时,具有如下意义

  • 被修饰的方法会将一个 Future 对象作为返回值
  • 该方法会同步执行其中的方法的代码直到第一个 await 关键字,然后它暂停该方法其他部分的执行;
  • 一旦由 await 关键字引用的 Future 任务执行完成,await的下一行代码将立即执行。
// 导入io库,调用sleep函数
import 'dart:io';

// 模拟耗时操作,调用sleep函数睡眠2秒
doTask() async{
  await sleep(const Duration(seconds:2));
  return "Ok";
}

// 定义一个函数用于包装
test() async {
  var r = await doTask();
  print(r);
}

void main(){
  print("main start");
  test();
  print("main end");
}

运行结果:

main start
main end
Ok

需要注意,async 不是并行执行,它是遵循Dart 事件循环规则来执行的,它仅仅是一个语法糖,简化Future API的使用。

本文分享自微信公众号 - 编程之路从0到1(artofprogram)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 学艺不精,总是掉坑!前后端分离历险记

    Spring Boot + Vue 这一对技术栈目前看来可以说是非常的火热,关于 Spring Boot 松哥已经写过多篇教程,如:

    江南一点雨
  • 前端开发使用工具 gulp

    gulp是基于流的前端构件化工具。gulp是自动化项目的构建利器;不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成。同时使用非...

    用户6029108
  • 发现Outlook安卓版本APP跨站漏洞CVE-2019-1105

    Outlook可能算是目前比较流行的邮箱APP之一了,近期,CyberArk公司研究团队就发现了Outlook安卓版本APP的一个跨站漏洞(XSS)- CVE-...

    FB客服
  • Medium博客平台从Stored XSS到账号劫持

    本文分享的是作者在博客平台Medium上编写文章时,偶然发现一个Stored XSS漏洞,在此基础上深入分析又发现了Account Takeover账号劫持漏洞...

    FB客服
  • 22道高频JavaScript手写面试题及答案

    由于防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

    桃翁
  • 「中高级前端进阶」从零开始手写一个 vue-cli 脚手架

    关注我的小伙伴应该知道,我之前写过一篇脚手架相关的文章,在掘金收获了近一千个赞,被前端大全和奇舞周刊公众号转载。

    前端达人
  • 调研Mybatis用到哪几种设计模式

    虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更...

    哲洛不闹
  • ES6的前世今生

    1996 年 11 月,Netscape 创造了javascript并将其提交给了标准化组织 ECMA,次年,ECMA 发布 262 号标准文件(ECMA-26...

    逆月翎
  • .NET 程序员如何学习Vue

    之所以取这个标题,是因为本文来自内部培训的整理,培训的对象是公司的 .NET 程序员,.NET 程序员学习 Vue 是为了在项目中做二次开发时能够更好地跟产品对...

    oec2003
  • 按键精灵——数组的应用(二)

    数组的应用,上一期讲了Array、UBound、LBound,这期我们继续将另外两个函数,Split函数和Filter函数。

    Ed_Frey

扫码关注云+社区

领取腾讯云代金券