在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。