dart基础之异步编程

在java中,有Thread来表明一个线程,可是在dart中是没有线程概念的,只有相似于多线程的isolate。除此以外,还针对读文件、数组操做专门制定了一个异步Stream类,使用起来很方便。本次主要分享一下dart中是如何进行异步操做的,这里跟java差异仍是蛮大的。
java

1、isolate数组

Dart是基于单线程模型的语言。可是在开发当中咱们常常会进行耗时操做好比网络请求,这种耗时操做会堵塞咱们的代码,因此在Dart也有并发机制,名叫isolate。APP的启动入口main函数就是一个相似Android主线程的一个主isolate。和Java的Thread不一样的是,Dart中的isolate没法共享内存,相似于Android中的多进程。网络

多说无益,直接上代码。以下图,咱们首先在主isolate中定义一个receivePort,而后将该receivePort中的sendPort对象以及新的isolate入口方法isolateMain传给了Isolate,至关于告诉新的isolate,你接下来执行这个isolateMain方法吧,和个人通讯方式是receivePort.sendPort。接下来再看看isolateMain方法,isolateMain方法中也new了一个receivePort对象传给了主isolate,也是告诉了主线程和它的通讯方式是经过这个sendPort来发送信息。具体的发送方式很简单,就是sendPort.send(msg)便可。接收消息是经过刚刚new出来的receivePort.listen()的方式,listen方法的回调参数是一个匿名方法,返回值就是其余isolate传过来的数据。多线程

须要注意的是,因为在不一样的isolate中是不共享内存的,这个和Android中的进程有点相似,因此当前isolate中的变量在其余isolate中是不可见的。在本例中,咱们在子isolate的入口函数中打印出子isolate中的i变量的值,结果为null,是由于i只在主isolate中赋值为10了,可是子isolate中并无赋值,也就是null了。那么应该如何让主isolate中的改动让子isolate可见呢,只须要在主isolate中发送一条消息让子isolate修改便可。另外,若是在全局范围内改也是对全部isolate生效的。
并发

2、event-loop异步

咱们首先看一个例子,在单个isolate中因为是在单线程中,因此顺序只能有一个并不能并行。以下例,咱们在主isolate中首先注册了监听,而后睡眠了2秒,重点来了,就算在2秒内收到了消息,可是仍是会等到那2秒睡眠时间过了之后才会执行收到的消息。最后的结果是首先输出null,而后2秒后再输入休眠完成,而后立刻跟上了那2条消息。
async



同Android Handler相似,在Dart运行环境中也是靠事件驱动的,经过event loop不停的从队列中获取消息或者事件来驱动整个应用的运行,isolate发过来的消息就是经过loop处理。可是不一样的是在Android中每一个线程只有一个Looper所对应的MessageQueue,而Dart中有两个队列,一个叫作event queue(事件队列),另外一个叫作microtask queue(微任务队列)。其中上面的例子中使用的是事件队列,事件队列的优先级不如微任务队列。函数

Dart在执行完main函数后,才会由Loop开始执行两个任务队列中的Event,这就是main方法内sleep会影响回调执行的缘由。首先Loop检查微服务队列,依次执行Event,当微服务队列执行完后,就检查Event queue队列依次执行,在执行Event queue的过程当中,每执行完一个Event就再检查一次微服务队列。因此微服务队列优先级高,能够利用微服务进行插队。对于这个特性,咱们能够再看一个例子。在该例子中,主isolate中有一个死循环,因此致使loop并不会去检查事件队列,因此这个输出永远都出不来,将一直卡在main()中。微服务

咱们再来验证一下微服务的插队功能,以下例,首先调用then方法将读文件加入到事件队列,而后开启了一个微服务。在这里执行的顺序是main->微服务->事件队列,因此结果是先输出future:excute microtask,而后输出被读文件中的内容。
oop


3、future

在 Dart 库中随处可见 Future 对象,一般异步函数返回的对象就是一个 Future。 当一个 future 执行完后,他里面的值 就可使用了,可使用 then() 来在 future 完成的时候执行其余代码。Future对象其实就表明了在事件队列中的一个事件的结果。

可能看这个说明有点晕乎,咱们看一下上面读文件那个例子中的then方法返回值吧,看到没返回值就是Future对象。也就是说事件队列的结果就是一个future,这里之因此拿出来说是由于dart中使用到的地方实在太多了。接下来一块儿看看future类中都提供了哪些功能吧。


1.异常捕获

经过future类能够捕获到时间队列中的错误,经过catchError方法来获取到回调。咱们知道在主Isolate中捕获异常能够用try...catch,其实catchError能够看作是异步的try...catch。

2.组合

then()的返回值一样是一个future对象,能够利用队列的原理进行组合异步任务。在本例中,先执行文件读取操做,而后再将返回值1进行输出,最后抓一下错误。至关于相似build模式,then能够一直点下去,上次的返回值就是本次的参数。

上面是一个一个任务执行,还有一种操做是等待多个任务都完成之后再执行其余操做。以下例,咱们定义了3个任务,第一个是读文件,第二个是延迟3秒,第三个是输出一些内容。因为使用了Future.wait方法将前2个任务绑定,因此结果是会等前2个任务完成之后才会执行第三个


4、Stream

Future 表示稍后得到的一个数据,全部异步的操做的返回值都用 Future 来表示。可是 Future 只能表示一次异步得到的数据。而 Stream 表示屡次异步得到的数据。好比 IO 处理的时候,每次只会读取一部分数据和一次性读取整个文件的内容相比,Stream 的好处是处理过程当中内存占用较小。而 File 的readAsString()是一次性读取整个文件的内容进来,虽然得到完整内容处理起来比较方便,可是若是文件很大的话就会致使内存占用过大的问题。

补充个例子可能更加具体,使用openRead来打开Stream流,而后监听流的变化,最后执行了屡次的回调。固然读取的文件要足够大,若是小的话也只会读取一次的。接下来继续使用future,以此来读取,两种方式都是能够的。


还有一个特性是可使用onData来对监听进行重置。以下例,获取到监听者对象后,对监听方法进行了替换,最后执行的是新方法,而老方法直接被丢弃。除了替换监听方法,还能够执行其余的方法,好比调用onDone来结束一个监听。



5、广播

Stream有两种订阅模式:单订阅和多订阅。单订阅就是只能有一个订阅者,上面的使用咱们都是单订阅模式,而广播是能够有多个订阅者。经过 Stream.asBroadcastStream() 能够将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性能够判断当前 Stream 所处的模式。

以下例,经过将asBroadcastStream将Stream转换成广播,而后就能够多处监听了。


须要注意的是,若是是直接建立的流管理器,就算是多订阅模式下,若是先发送而后再注册,是不会接收到消息的。


可是若是是经过asBroadcastStream来获取到streamCOntroller,先发送再注册,也是会接收到消息的。利用这个特性,能够用来实现sticky粘性广播。


6、async/await

使用'async'和'await'的代码是异步的,可是看起来很像同步代码。当咱们须要得到A的结果,再执行B,时,你须要'then()->then()',可是利用'async'与'await'可以很是好的解决回调地狱的问题。

在下例中,咱们将readFile用async进行修饰,表明该方法将使用同步关键字await。接下来咱们读取了2次文件,都用await修饰,表明这两次操做都以同步的方式运行,只有在第一行执行完之后才进入第二行代码的执行。


最后,给你们带来使用stream手撸一个eventbus的代码。在注册时将Stream传出到调用处,而后就能够调用Stream的listen方法来获取消息。发送消息只须要调用StreamController的add方法便可,因为考虑到须要往不一样的事件中发送消息,因此用了一个map来保存全部的订阅类型。另外,为了方便调用,这里使用了一个单例模式,对外公开了getInstance方法用来将单例对象传递给外界。取消注册是先关闭须要取消订阅的streamcontroller,而后从map移除


总结:本次介绍了异步操做isolate的使用,以及dart中事件队列、微服务队列的相关知识,而后了解了future对象是事件队列的结果,接下来对比了stream和future的区别,而后介绍了广播的直接建立、stream建立方式以及区别,接下来又介绍了async、await的结合使用方法,最后分享了本身的一个异步实战-使用Stream手写Eventbus。