Dart中的异步与事件循环

2,373 阅读5分钟

前言

Dart作为Flutter的开发语言,了解Dart的机制是必可少的。本篇文章就介绍一下Dart的异步操作与事件循环机制。

异步操作我们都知道在开发过程中,如果有耗时操作,我们一般都会使用异步任务解决,以防主线程卡顿。

事件循环是Dart中处理事件的一种机制。Flutter中就是通过事件循环来驱动程序的运行,这点与Android中的Handler有点类似。

Dart的事件循环机制

Dart语言是单线程模型的语言。这也就意味着Dart在同一时刻只能执行一个操作,其他操作在这个操作之后执行。那么Dart的其他操作如何执行呢?在Dart中通过Event Loop来驱动事件的执行。

  • Dart程序启动时会创建两个队列,分别是MicroTak队列(微服务队列)和Event队列(事件队列);
  • 然后执行main()方法;
  • 最后启动事件循环;

上面的图片描述了Dart的事件循环机制。可以看到,Dart执行事件的流程是:

  1. 执行main();
  2. 执行MicroTask队列中事件;
  3. 执行事件队列中的事件;

可以看到,main()方法的优先级最高,执行结束后,才执行两个队列中事件。其中MicroTask队列的优先级又高于Event队列的优先级。

MicroTask队列和Event队列

MicroTask 队列适用于非常简短且需要异步执行的内部动作或者想要在Event执行前做某些操作,因为在MicroTask执行完以后,还需要执行Event队列中事件。

在使用Dart时异步事件应该优先考虑使用Event队列。

Dart中的异步

Future

Future 是一个异步执行并且在未来的某一个时刻完成(或失败)的任务。每一个Future都会执行一个事件并且将事件加入到Event队列中去。

factory Future(FutureOr<T> computation()) {
    _Future<T> result = new _Future<T>();
    Timer.run(() {
      try {
        result._complete(computation());
      } catch (e, s) {
        _completeWithErrorCallback(result, e, s);
      }
    });
    return result;
  }

从上面Future的构造函数代码可以看到,通过Timer的run()方法将运算过程发送到事件队列中去,然后通过_complete()方法获取了提交给Future的运算结果,最终返回一个Future对象。

void main(){
    print('Before the Future');
    Future((){
        print('Running the Future');
    }).then((_){
        print('Future is complete');
    });
    print('After the Future');
    Future<String>.microtask(() {
        return "microtask";
  	}).then((String value) {
    	print(value);
  	});
}

以上代码的运行结果是:

Before the Future
After the Future
microtask
Running the Future
Future is complete

可以看到,这里先执行的是main()方法中的打印信息,然后打印了microtask,最后才执行了Future。这里也可以证明Future是在事件队列中执行的。

async、await关键字

async与await关键字配合使用可以更加优雅的处理dart中的异步。对于有async修饰的函数来说,它会同步执行函数中的代码,直到遇到第一个await关键字,然后将await修饰的代码提交到事件队列中处理,在await修饰的代码执行完毕后会立即执行剩下的代码,而剩下的代码将在微任务队列中执行。

void methodAsync() async {
  sleep(Duration(seconds: 1));
  Future<String>(() {
    return "future 1";
  }).then((String value) {
    print(value);
  });
  print("async start");
  await microTaskAndEvent(); //输出一个微任务打印 microtask 以及一个事件队列任务打印 futrue
  print("async end");
}

void main() {
    methodAsync();
  	print("Hello World");
}

以上代码的输出结果是:

async start
Hello World
microtask
async end
future 1
future

从打印结果可以看出,

  • 这里同步执行methodAsync()方法,即使在睡眠1s的情况下,也首先输出了async start;然后输出Hello World;
  • 接下来执行了一个Future1,然后执行await关键字后面的函数;
  • microTaskAndEvent()方法包括一个微任务和事件。这里可以看到打印了microtask接着打印了async end,这里可以证明async在await关键字之后的代码是放到微任务中执行的。
  • 最后打印了事件队列中的任务。

Isolate

Isolate从名称上理解是独立的意思。在dart中,Isolate有着自己的内存并且不存在通过共享内存的方式实现Isolate之间的通信,同时Isolate是单线程的计算过程。

Isolate的特点与使用:

  • Isolate有着自己的内存并且不存在通过共享内存的方式实现Isolate之间的通信;
  • 每个Isolate都有自己的事件循环;
  • 在Isolate中是通过Port进行通信的。Port分为两种一种是receive port用于接收消息。另外一种是send port用于发送消息;
  • 使用Isolate时,可以通过spwan()方法启动一个Isolate

接下来通过一个例子展示Isolate的通信以及创建过程。

main() async {
  var receivePort = new ReceivePort();
  /**
    * 初始化另外一个Isolate,
    * 将main isolate的send port发送到child isolate中,
    * 用于向main isolate发送信息
    */
  await Isolate.spawn(echo, receivePort.sendPort);

  // child isolate的第一个信息作为child isolate的send port,由于向child isolate发送信息
  var sendPort = await receivePort.first;

  var msg = await sendReceive(sendPort, "hello");
  print('received $msg');
  msg = await sendReceive(sendPort, "close");
  print('received $msg');
}

// child isolate的入口
echo(SendPort sendPort) async {
  // 打开一个接收端口
  var port = new ReceivePort();
  //监听接收的信息
  port.listen((dynamic msg) {
    print("child receive $msg");
    var data = msg[0];
    SendPort replyTo = msg[1];
    replyTo.send(data);
    if (data == "close") port.close(); //关闭接收端口
  });

  // 向其他的isolate通知child isolate的发送端口.
  sendPort.send(port.sendPort);
}
//发送信息
Future sendReceive(SendPort port, msg) {
  ReceivePort response = new ReceivePort();
  port.send([msg, response.sendPort]);
  return response.first;
}

总结

以上就是关于Dart的异步以及事件循环原理。理解这些内容是必要的,因为在很多的开发场景都会用到异步,同时需要很好的理解Dart的事件处理机制。