异步编程API
首先,dart2已经从语言层面支持了更好的异步编程语法 - 利用async和await,可以写出更加直白简单的异步执行代码。从某种意义上,async和await可以看作是Future的语法糖,但又不完全是,因为async和await是可以通过底层进行优化实现的,并不一定必须要依赖Future。学习异步编程API,主要是为了理解已有的老代码,dart2之后的代码应该尽可能使用新的语法完成异步编程,提高可读性和可维护性。
Async
Future
https://api.dart.dev/stable/2.7.1/dart-async/Future-class.html
这里讨论的是Future类提供的接口,但实际上dart现在已经对于Future提供了语言层面上的语法糖,支持使用async和await来以同步风格编写Future:https://dart.dev/codelabs/async-await
要创建Future对象,可以直接new一个Future对象并传入一个lambda:new Future(foo),Future会利用Timer.run异步的执行foo,并利用foo的返回值或执行过程中抛出的异常作为结果,触发then或者catchError。如果lambda返回的是一个Future类型的对象,会继续执行这个Future直到返回的是个普通对象为止。
另一种创建Future对象的方式是使用Completor类型,每个Completor类型的对象内都定义了一个future属性,以及一对complete和completeError方法,当调用这两个方法时,就会触发future的then或catchError回调。Completer一般用于将回调风格的API转化为Future,但还有另一个非常好用的场景:当需要确保一系列不属于同一个执行流程的逻辑上相关的异步流程串行执行时,可以在每个异步流程开始时创建一个Completer,后面的流程要想启动必须要await前一个流程创建的Completer。
Stream
https://api.dart.dev/stable/2.7.2/dart-async/Stream-class.html
这里是一个翻译过来的对Stream的介绍:https://dart.cn/articles/libraries/creating-streams
从个人的角度,Stream相比其他API,从外部看接口比较清晰,但内部隐藏的细节较多。
一个Stream就是不断生成对象的流,要遍历Stream生成的所有对象,可以使用await for语法:
await for (var chunk in stream) {…}
要创建stream,有三种方式:基于现有的Stream、使用async*函数创建stream、使用StreamController生成stream。
- 转换现有的stream:使用map、where、expand、take等方法来对原stream进行转换,生成新的stream;
- 创建新的Stream:
- 使用yield和async关键字创建stream:
- 在dart中支持异步生成器async,异步生成器函数被调用时会创建一个stream,函数体在stream被监听时开始运行。当函数返回时,stream关闭,函数执行过程中可以使用yield或者yield向stream提交事件。当stream被取消监听时,生成器函数会在执行到下一个yield语句时返回,然后stream#cancel方法返回的Future完成。
- yield和yield的区别是,yield用于向stream提交对象,而yield用于调用另一个生成器,将另一个生成器提交的对象提交给当前生成器对应的stream,如果在应该使用yield*的地方使用了yield,就相当于给stream提交了另一个stream对象。
- 使用StreamController:
- 很多时候stream的事件无法用一个简单的生成器函数生成,而是来自于程序的各个部分,这种使用需要使用StreamController来创建和填充stream。
- 一般来说StreamController都应该利用onListen和onPause来在被监听时开始生成事件,在被暂停时暂时不生成事件,但具体实现也可以不这么做。应该了解不遵守规则的后果:StreamController会缓存所有生成了但还无法被派发给监听者的事件,如果在没有监听者,或者在暂停的情况下不断生成事件,最终事件会在StreamController中堆积,导致内存问题。
- StreamSink是一个对StreamController的事件生成部分的接口定义,当希望某个对象只需要负责生成事件,而不需要关心StreamController的其他方面时,可以将StreamSink对象(#sink)提供给对象使用。
- dart还支持同步的StreamController,但业务不应该使用这个版本。
Zone
- 使用yield和async关键字创建stream:
Timer
https://api.dart.dev/stable/2.7.2/dart-async/Timer-class.html
Timer用于设置倒计时来一次性的或重复的触发某个回调。
开启一个倒计时很简单:new Timer(duration, handleTimeout)
使用Timer时,一定考虑边界情况,如果倒计时还没到0,就已经退出了当前上下文,导致倒计时不需要了,要及时使用cancel取消Timer。
顺便一提,dart的Future.delayed方法就是用Timer实现的,在不需要cancel的情况下,使用这个API来定时可能会更方便一些。
同步的Future和Stream
Dart中,使用Completer和StreamController提供的接口可以创建新的Stream或者Future对象,在创建方法中,我们要传入一个参数(通常是isAsync之类的)指定要创建的对象表现出同步还是异步的行为,这是什么意思?
同步行为,指的是当控制Stream的新内容抵达时,立刻同步的去通知Listener;当Future resolve时,也是一样。但是,使用Stream或者Future的用户并不期望这样的行为,这些API只有在异步场景下使用才有意义,因此,用户期望的行为是回调不会立即触发,而是等到当前EventLoop单元被执行完毕后,再在未来的某个EventLoop中执行回调。因此,通常我们应该不使用同步行为。
但是,异步执行在某些情况下是不必要的,如果能够确保用户的代码兼容同步行为,那么同步行为能够更早的令事件被接收到。库的实现人员在某些场景下会需要对事件进行转发,如果库的实现者能够确保自己的代码兼容同步行为,并且能够确保最终这个事件通知到库的用户时,会遵守异步行为,就可以利用同步行为来减少事件从触发到最终通知到用户的时间。不过,这么做之前考虑清楚,这很可能是过度优化,并且导致相关代码非常脆弱!
dart语言异步关键字
dart支持使用async和await实现语义更清晰的异步编程,让异步代码像同步代码一样编写,并且支持try-catch语句,能够在debug或者在抛出的exception中看到完整的异步调用栈。
dart语言线程支持
https://api.dart.dev/stable/2.7.1/dart-isolate/dart-isolate-library.html
和JS一样,dart也是一个基于单线程消息队列模型的语言,但是和JS不一样的是,dart一开始就支持从dart层面使用线程能力,即Isolate,熟悉JS的人可能会想起来Worker。
dart:isolate库是由dartvm自带的基础库,用于支持使用isolate进行并发编程,isolate和线程类似,但isolate不共享内存,相互之间只能通过message通信。
Isolate
https://api.dart.dev/stable/2.7.2/dart-isolate/Isolate-class.html
Isolate代表Dart执行环境。
所有dart代码都在某个isolate环境中执行,isolate中的代码只能访问到这个isolate中的变量。不同的isolate间可以通过port发送数据来交互(ReceivePort,SendPort)。
可以在当前Isolate内通过另一个Isolate对象引用来控制它。每个Isolate都运行着独立的Event Loop,并且每个Event还可以注册microtask,这些microtask会在这个Event处理完毕后被执行,可以理解为每个Event都有一个内嵌的microtask loop。
Isolate允许其他Isolate控制本Isolate内的Event loop,包括当遇到异常时,允许暂停当前Isolate并且获取Event Loop中的所有事件。
controlPort属性(SendPort实例)提供了控制Isolate的接口,要想控制Isolate,Isolate的Capability必须执行对应的操作,比如pauseCapability和terminateCapability,如果Isolate创建时没有设置好Capability,那么Isolate就不支持对应的这类操作。
Isolate在创建时,要求传入一个SendPort作为controlPort以及一组Capability决定controlPort允许执行的操作。一个Isolate不能通过SendPort在不同的Isolate间传输,但controlPort和Capability可以。
dart语言IO支持
https://api.dart.dev/stable/2.7.1/dart-io/dart-io-library.html