dart基礎之非同步程式設計

你的使用者名稱發表於2019-03-13

在java中,有Thread來代表一個執行緒,但是在dart中是沒有執行緒概念的,只有類似於多執行緒的isolate。除此之外,還針對讀檔案、陣列操作專門制定了一個非同步Stream類,使用起來很方便。本次主要分享一下dart中是如何進行非同步操作的,這裡跟java差別還是蠻大的。

一、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生效的。

dart基礎之非同步程式設計

二、event-loop

我們首先看一個例子,在單個isolate中由於是在單執行緒中,所以順序只能有一個並不能並行。如下例,我們在主isolate中首先註冊了監聽,然後睡眠了2秒,重點來了,就算在2秒內收到了訊息,但是還是會等到那2秒睡眠時間過了以後才會執行收到的訊息。最後的結果是首先輸出null,然後2秒後再輸入休眠完成,然後馬上跟上了那2條訊息。

dart基礎之非同步程式設計

dart基礎之非同步程式設計

同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()中。

dart基礎之非同步程式設計

我們再來驗證一下微服務的插隊功能,如下例,首先呼叫then方法將讀檔案加入到事件佇列,然後開啟了一個微服務。在這裡執行的順序是main->微服務->事件佇列,所以結果是先輸出future:excute microtask,然後輸出被讀檔案中的內容。

dart基礎之非同步程式設計

三、future

在 Dart 庫中隨處可見 Future 物件,通常非同步函式返回的物件就是一個 Future。 當一個 future 執行完後,他裡面的值 就可以使用了,可以使用 then() 來在 future 完成的時候執行其他程式碼。Future物件其實就代表了在事件佇列中的一個事件的結果。

可能看這個說明有點暈乎,我們看一下上面讀檔案那個例子中的then方法返回值吧,看到沒返回值就是Future物件。也就是說事件佇列的結果就是一個future,這裡之所以拿出來講是因為dart中使用到的地方實在太多了。接下來一起看看future類中都提供了哪些功能吧。

dart基礎之非同步程式設計

1.異常捕獲

通過future類可以捕獲到時間佇列中的錯誤,通過catchError方法來獲取到回撥。我們知道在主Isolate中捕獲異常可以用try...catch,其實catchError可以看做是非同步的try...catch。

dart基礎之非同步程式設計

2.組合

then()的返回值同樣是一個future物件,可以利用佇列的原理進行組合非同步任務。在本例中,先執行檔案讀取操作,然後再將返回值1進行輸出,最後抓一下錯誤。相當於類似build模式,then可以一直點下去,上次的返回值就是本次的引數。

dart基礎之非同步程式設計

上面是一個一個任務執行,還有一種操作是等待多個任務都完成以後再執行其他操作。如下例,我們定義了3個任務,第一個是讀檔案,第二個是延遲3秒,第三個是輸出一些內容。由於使用了Future.wait方法將前2個任務繫結,所以結果是會等前2個任務完成以後才會執行第三個

dart基礎之非同步程式設計

四、Stream

Future 表示稍後獲得的一個資料,所有非同步的操作的返回值都用 Future 來表示。但是 Future 只能表示一次非同步獲得的資料。而 Stream 表示多次非同步獲得的資料。比如 IO 處理的時候,每次只會讀取一部分資料和一次性讀取整個檔案的內容相比,Stream 的好處是處理過程中記憶體佔用較小。而 File 的readAsString()是一次性讀取整個檔案的內容進來,雖然獲得完整內容處理起來比較方便,但是如果檔案很大的話就會導致記憶體佔用過大的問題。

補充個例子可能更加具體,使用openRead來開啟Stream流,然後監聽流的變化,最後執行了多次的回撥。當然讀取的檔案要足夠大,如果小的話也只會讀取一次的。接下來繼續使用future,以此來讀取,兩種方式都是可以的。

dart基礎之非同步程式設計

還有一個特性是可以使用onData來對監聽進行重置。如下例,獲取到監聽者物件後,對監聽方法進行了替換,最後執行的是新方法,而老方法直接被丟棄。除了替換監聽方法,還可以執行其他的方法,比如呼叫onDone來結束一個監聽。

dart基礎之非同步程式設計

dart基礎之非同步程式設計

五、廣播

Stream有兩種訂閱模式:單訂閱和多訂閱。單訂閱就是隻能有一個訂閱者,上面的使用我們都是單訂閱模式,而廣播是可以有多個訂閱者。通過 Stream.asBroadcastStream() 可以將一個單訂閱模式的 Stream 轉換成一個多訂閱模式的 Stream,isBroadcast 屬性可以判斷當前 Stream 所處的模式。

如下例,通過將asBroadcastStream將Stream轉換成廣播,然後就可以多處監聽了。

dart基礎之非同步程式設計

需要注意的是,如果是直接建立的流管理器,就算是多訂閱模式下,如果先傳送然後再註冊,是不會接收到訊息的。

dart基礎之非同步程式設計

但是如果是通過asBroadcastStream來獲取到streamCOntroller,先傳送再註冊,也是會接收到訊息的。利用這個特性,可以用來實現sticky粘性廣播。

dart基礎之非同步程式設計

六、async/await

使用'async'和'await'的程式碼是非同步的,但是看起來很像同步程式碼。當我們需要獲得A的結果,再執行B,時,你需要'then()->then()',但是利用'async'與'await'能夠非常好的解決回撥地獄的問題。

在下例中,我們將readFile用async進行修飾,代表該方法將使用同步關鍵字await。接下來我們讀取了2次檔案,都用await修飾,代表這兩次操作都以同步的方式執行,只有在第一行執行完以後才進入第二行程式碼的執行。

dart基礎之非同步程式設計

最後,給大家帶來使用stream手擼一個eventbus的程式碼。在註冊時將Stream傳出到呼叫處,然後就可以呼叫Stream的listen方法來獲取訊息。傳送訊息只需要呼叫StreamController的add方法即可,由於考慮到需要往不同的事件中傳送訊息,所以用了一個map來儲存所有的訂閱型別。另外,為了方便呼叫,這裡使用了一個單例模式,對外公開了getInstance方法用來將單例物件傳遞給外界。取消註冊是先關閉需要取消訂閱的streamcontroller,然後從map移除

dart基礎之非同步程式設計

總結:本次介紹了非同步操作isolate的使用,以及dart中事件佇列、微服務佇列的相關知識,然後瞭解了future物件是事件佇列的結果,接下來對比了stream和future的區別,然後介紹了廣播的直接建立、stream建立方式以及區別,接下來又介紹了async、await的結合使用方法,最後分享了自己的一個非同步實戰-使用Stream手寫Eventbus。

相關文章