Dart 语言入门——异步与并发编程

一、异步编程 Future
有时候我们需要让一个调用异步执行 ,比如一个比较耗时的操作,好继续后面的处理,而不是一直等待处理完成。异步执行就是调用本身立即返回,并在稍后的某个时候执行完成时再获得返回结果。在 Dart 中,这是通过 Future<T> 对象实现的。我们可以通过 then() 方法注册一个回调函数在成功执行完成时调用,并获得返回值。如果执行失败,则会抛出异常。另外也可以通过 handleException() 注册遇到异常时执行的回调函数,如果这个回调函数返回 true ,那么 Future 认为异常已被很好地处理了就不再抛出异常,否则还是会抛出异常。但一般不必注册 handleException,因为异常会正常抛出给外面的代码。onComplete 注册的回调函数不管成功还是失败都会执行。

当 Future 完成时,依次执行以下动作:
  1. 如果成功完成,那么执行 then 方法中注册的回调函数
  2. 如果执行失败,那么依次执行 handleException 方法中注册的回调函数,直到其中一个返回 true
  3. 执行 onComplete 方法中注册的回调函数
  4. 如果执行失败,并且 then 方法中至少注册了一个回调函数,并且没有一个 handleException 方法中注册的回调函数返回 true,那么就抛出异常。

使用 Completer 对象可以创建一个 Future,并在之后为 Future 提供一个返回值。

Future<bool> longExpensiveSearch() { var completer = new Completer(); // 这里执行较为耗时的操作 // ... completer.complete(true); return completer.future; } main() { var future = longExpensiveSearch(); // 立即返回 future.then((value) { // 注册成功时的回调函数并立即返回 print(value); }); }


Dart 还提供了多个异步方法的链式调用,详见  Future.chain() 方法。如果要等待多个异步方法全部结束,可以使用  Futures.wait() 方法。


二、并发编程 Isolate
Dart 没有并发时的共享状态,所有 Dart 代码都是 Isolate 中运行的,包括最初的 main() Isolate(也称为 root Isolate)。Dart 内建了 Isolate 机制,类似于 Actor ,仅在端口(Port)上通过消息进行通信。 每个 Isolate 有它自己的堆(Heap)和栈(Stack),彼此隔离。消息在接收前被复制,这样 Isolate 之间就无法操作相同的对象了。因为状态是彼此隔离的,所以这种并发编程模式不需要锁、互斥量什么的。

每个 Isolate 有它自己的堆内存,这意味着其中所有内存中的值,包括全局数据,都仅对该  Isolate 可见。 Isolate 之间的通信只能通过传递消息的机制完成。消息通过端口(port)收发。

Isolate 只是一个概念,具体一个 Isolate 是什么取决于如何实现。比如,在 Dart VM 中一个 Isolate 可能是会是一个线程,在 Web 中可能会是一个 Web Worker 。


基本概念

要使用 Isolate, 需要导入 dart:isolate 库。它主要包括:
  • 顶层变量 port ,即一个 ReceivePort 对象实例,代表当前 Isolate 的初始接收端口
  • 顶层函数 spawnUri() 和 spawnFunction() ,用于创建新的 Isolate
  • SendPort 和 ReceivePort 类,分别代表发送端口和接收端口

发送消息使用 SendPort ,接收消息使用 ReceivePort 。每个 Isolate 在建立之初都有一个默认的  ReceivePort,包括最初 main() 所在的 root Isolate。这个默认的 ReceivePort 通过 dart:isolate 中的顶层变量 port 获得。每个   Isolate  除了默认的   ReceivePort 外,还可以随时使用构造函数 new ReceivePort()  创建其它    ReceivePort 。通过 ReceivePort 的 toSendPort() 方法又可以创建一个 SendPort ,通过发送端口发送的消息都会传递给对应的接收端口。一个接收端口可以有多个对应的发送端口。发送端口本身也可以作为消息发送给其它的 Isolate,其它 Isolate 使用发送端口给对应 Isolate 发送消息。

小结:每个 Dart 程序由一个或多个 Isolate 组成,Isolate 又可以创建其它 Isolate。每个 Isolate 可以有一个或多个接收端口,每个接收端口又可以有一个或多个对应的发送端口,Isolate 之间依靠发送端口和接收端口收发消息进行通信。


创建 Isolate
dart:isolate 中的顶层函数 spawnFunction() 用于创建一个新的 Isolate,它接收一个函数作为新 Isolate 的入口点,新的 Isolate 与当前 Isolate 使用相同的代码,但是从传递的函数开始执行。

任何顶层函数都可以作为 Isolate 的入口点 ,但不能用闭包函数。 这个入口点应该是一个无参数并返回 void 的函数。把入口点作为参数传给spawnFunction() 函数就创建了一个 Isolate,并开始执行这个函数。

import 'dart:isolate';

echo() { print('hello'); } main() { spawnFunction(echo); // 以 echo 函数为入口,创建一个新的 Isolate

}

注意:上面的程序还有点问题,echo() 函数可能执行了,也可能没执行。这是因为 main() 执行完 spawnFunction() 后就立即结束了,这样 root Isolate 退出,整个 Dart 程序便退出了。具体请见后面的生命周期介绍。


发送消息
调用  spawnFunction()  会返回一个与新 Isolate 进行通信用的发送端口  SendPort ,这个发送端口来自于新 Isolate 的默认接收端口 。有了这个发送端口,我们便可以向新的 Isolate 发送消息了。

消息内容可以是:
  • 基本类型的值(null、num、bool、double、String)
  • SendPort 对象实例
  • 以上这些元素组成的 List 或 Map,其中可以继续嵌入 List 或 Map
  • 特定情况下可以是任意对象
特定情况是指:如果两个 Isolate 共享相同的代码,并且运行在同一个进程中,比如使用 spawnFunction() 创建的 Isolate ,那么也可以发送任意对象。当然,发送的同样是对象的副本,而不会共享对象。发送任意对象目前只被 Dart VM 支持,dart2js 只支持上面前三种说的消息类型。

下面使用 SendPort 的  send() 方法发送一个异步消息:

import 'dart:isolate'; echo() { // 使用 port 接收消息 } main() { var sendPort = spawnFunction(echo); sendPort.send('Hello from main'); }

send() 方法的第一个参数就是要发送的消息。另外, send() 方法还有一个可选的 SendPort 参数作为回复地址一起发送给接收者。这样接收者便可以使用这个发送端口 回复 消息了。你也可以传递另一个 Isolate 的发送端口,让接收者直接回复给别人而不是自己。

import 'dart:isolate';

echo() {
// 使用 port 接收并且回复消息
}

main() {
var sendPort = spawnFunction(echo);

// 这里省略了接收的回复消息的代码
sendPort.send('Hello from main', port.toSendPort());

}

在上面的代码中,我们在 send() 方法里加上了一个回复地址,这样我们就可以在 main Isolate中接收 echo Isolate 给我们的回复消息了 。

这个回复地址  SendPort 是通过默认端口 port 的  toSendPort() 方法创建的, 当然,我们也可以新建一个接收端口,再用这个接收端口创建 回复地址  SendPort  。下面的代码和上面的代码效果一样。

import 'dart:isolate'; echo() { // 使用 port 接收并且回复消息 } main() { var sendPort = spawnFunction(echo); // 这里省略了接收的回复消息的代码

var receivePort = new ReceivePort(); sendPort.send('Hello from main', receivePort.toSendPort()); }

上面的示例省略了 main() 中接收消息的代码。实际上,如果你要发送一个消息并等待接收回复,还可以使用 SendPort 提供的另一个方法 call() ,它可以让你省去编写接收代码。 call() 方法会发送一条消息,并返回一个 Future 对象用于接收 Isolate 的回复消息。

import 'dart:isolate'; echo() { // 使用 port 接收并且回复消息 } main() { var sendPort = spawnFunction(echo); sendPort.call('Hello from main').then((reply) { print(reply); }); }

实际上,call() 方法创建了一个新的接收端口,然后用这个接收端口的发送端口作为回复地址,当收到回复时,接收端口就关闭并返回 Future 完成。之后,我们就可以用 Future 的 then() 方法来处理收到的回复消息了。


接收消息
使用 ReceivePort 的 receive() 方法就可以接收消息,这个方法接收一个回调函数参数,回调函数中有两个参数:接收的消息和回复地址,回复地址就是发送消息时提供的 SendPort 。

import 'dart:isolate'; echo(){ print('start echo'); port.receive((msg, replyTo){ // 接收消息 print('echo receive: $msg'); if(replyTo != null){ replyTo.send('hello $msg'); } }); } main() { var sendPort = spawnFunction(echo); sendPort.call('hanguokai').then((reply) { print(reply); }); }

因为发送消息时,对方可能没有提供回复地址,所以应该检查一下。


Isolate 与 Dart 程序的生命周期

Dart 程序首先从 main() 函数开始,这也是 Dart 中的第一个 Isolate,被称为 root Isolate,root Isolate 结束了整个 Dart 程序就结束了, 不管是否还有其它正在运行的 Isolate。

对 Isolate 而言, 在 Dart VM 中只要  Isolate 中还有打开的 ReceivePort,它就会一直运行。也就是说,如果在 Isolate 中调用了接收端口的 receive() 方法,那么这个 Isolate 就会一直运行,直到接口端口的 close() 方法被调用。如果 Isolate 中没有调用 receive() 方法,那么入口函数执行完这个 Isolate 就结束了。另外,receive() 方法本身是异步的,它后面的代码会继续执行。

前面使用 send() 方法发送消息的示例代码都有一个问题,就是没有在 root Isolate 中等待 echo Isolate 结束,这样导致程序很可能提前退出了。要阻止程序退出很简单,只要让  root Isolate 保持打开状态即可,等其它 Isolate 通知它结束再结束。下面是一个完整示例:

import 'dart:isolate';

echo(){ print('start echo'); port.receive((msg, replyTo){ print('echo receive: $msg'); if(replyTo != null){ replyTo.send('hello $msg'); } }); }

main() {
var sendPort = spawnFunction(echo); port.receive((msg, _){ // 在 main 中接收消息,阻止程序退出 print('main receive: $msg'); print('exit'); port.close(); // 收到消息后,关闭接收端口,让程序退出 }); sendPort.send('hanguokai', port.toSendPort());
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值