Flutter開發之Dart線程與異步

談到異步,可能大家多會想到多線程,然而Dart是基於事件循環機制單線程模型

單線程?嗯哼,也就是說在Dart的世界裏沒有多線程之說,當然也沒有了所謂的主線程和子線程之分。

那麼,Dart是如何實現異步的呢?本文就以下介紹流程幫助你認識Dart異步。

同步與異步
Dart事件循環機制
Isolate設計

同步與異步

同步

同一線程中,按照代碼的編寫順序,自上而下依次執行。不過,在遇到如網絡請求、IO等耗時操作時會造成線程阻塞,後面的代碼遲遲得不到執行的問題。

對於耗時操作帶來的問題,我們很容易想到用異步去解決。Dart也是這樣嗎?不要被這一問矇蔽了你純真的雙眼,當然是啊,小仙女。

異步

代碼執行中,某段代碼的執行並不會影響後面代碼的執行。

說得好像有點抽象,emm… 讓我們看看異步的實現方式~

  • 多線程

開啓另一條線程執行一段耗時代碼,這樣兩條線程可以並列執行,自然不會阻塞線程而影響後面代碼的執行了。

多線程在適量併合理地使用下,可以說真香。但是其缺點也是顯而易見的:

  1. 開啓線程會帶來額外的資源和性能消耗,在遇到大量併發時,會給服務器帶來極大的壓力。
  2. 多個線程操作共享內存時需要加鎖控制,鎖競爭會降低性能和效率,複雜情況下還容易造成死鎖。
  • 單線程模型

一條執行線上,同時且只能執行一個任務(事件),其他任務都必須在後面排隊等待被執行。也就是說,在一條執行線上,爲了不阻礙代碼的執行,每遇到的耗時任務都會被掛起放入任務隊列,待執行結束後再按放入順序依次執行隊列上的任務,從而達到異步效果。

上述執行線爲什麼不直接說是線程?因爲Dart沒有線程概念,只有Isolate。所以,本文命題(Flutter…Dart線程…)就是錯的,目的就是引起童鞋們的注意。當然,在此方面理解爲線程,甚至理解爲一個函數體也沒毛病…

單線程模型的優勢就是避免了上述多線程的缺點,然而這種方式比較適合於往往把時間浪費在等待對方傳送數據或者返回結果的耗時操作,如網絡請求,IO流操作等。對於儘可能利用處理器的多核實現並行計算的計算密集型操作相對來說多線程更爲合適。

Dart事件循環機制

上文說了Dart是基於事件循環機制單線程模型,那麼問題來了…

爲什麼要採用單線程模型?

App使用過程中,多數時間處於空閒狀態,並不需要進行密集或高併發的處理,計算以及UI渲染,多線程方式顯然有些多餘~~~

爲什麼要基於事件循環機制?

對於用戶點擊,滑動,硬盤IO訪問等等事件,你不知道何時發生或以什麼順序發生,所以得有一個永不停歇且不能阻塞的循環來等待並處理這些“突發”事件。

基於以上,基於事件循環機制的單線程模型孕育而生 ↓↓↓

Dart事件循環機制是怎樣的?

Dart事件循環機制是由一個 消息循環(Event looper) 和兩個消息隊列:事件隊列(Event queue)微任務隊列(MicroTask queue) 構成。

  • Event Looper

Dart代碼的運行是從main函數開始的,main函數執行完後,Event looper開始工作,Event looper優先全部執行完Microtask queue中的event
直到Microtask queue爲空時,纔會執行Event queue中的event,後者爲空時才可以退出循環,這裏強調“可以”而不是“一定”要退出,視場景而定。

  • Event Queue

該隊列事件來源於外部事件Future

  1. 外部事件

例如:輸入/輸出,手勢,繪製,計時器,Stream等等;
對於外部事件,一旦沒有任何microtask要執行,Event looper就會考慮將列爲隊列中的第一項並執行它。

  1. Future

用於自定義Event queue事件。

通過創建Future類實例來向Event queue添加事件:

new Future(() {
  // 事件任務
});

延時5秒後添加一個事件:

new Future.delayed(const Duration(seconds:5), () {
  // 事件任務
});

如果該任務前面有其它任務需要先執行,該任務被執行的時間會大於5秒(單線程模型的缺陷之一,不能基於時鐘調度)。

這裏拓展下Future的一些用法(瞭解下就可以了,不是本文重點):

new Future(() => doTask) // 執行異步任務
    .then((result1) => doChildTask1(result1)) // doTask執行完後的子任務,result爲上個任務doTask的返回值
    .then((result2) => doChildTask2(result2)) // doChildTask1執行完後的子任務,result爲上個任務doChildTask1的返回值
    .whenComplete(() => doComplete); // 當所有任務完成後的回調函數

事件任務執行完後會立即依次執行then子任務,最後執行whenComplete函數。

  • Microtask Queue
  1. 上文已述,Microtask queue的優先級要高於Event queue
  2. 使用場景:想要在稍後完成一些任務(microtask)但又希望在執行下一個事件(event)之前執行。

Microtask一般用於非常短的內部異步動作,並且任務量非常少,如果微任務非常多,就會造成Event queue排不上隊,會阻塞Event queue的執行(如,用戶點擊沒有反應)。所以,大多數情況下優先考慮使用Event queue,整個Flutter源代碼僅引用scheduleMicroTask()方法7次。

通過創建scheduleMicrotask函數來向Microtask queue添加任務:

scheduleMicrotask(() {
  // 事件任務
});

Isolate

所有的Dart代碼都是在isolate中運行,它就像是機器上的一個小空間,具有自己的私有內存塊和一個運行着Event looper的單個線程。正如上文強調的:Dart中沒有線程的概念只有isolate

isolate具有自己的內存和運行事件循環的單個執行線程

每個isolate都是相互隔離(獨立)的,並不像線程那樣可以共享內存,isolate本身就是隔離的意思…

許多Dart應用都在單個isolate中運行所有代碼,但是如果特殊需要,您可以擁有多個。

兩個isolate,每個isolate都有自己的內存和執行線程

Isolate間可以一起工作的唯一方法是通過來回傳遞消息。一個isolate將消息發送到另一個isolate,接收者使用其Event looper處理該消息。

最後嘮叨

一個Dart應用是從Main isolate的main函數開始的,main函數執行完後,Event looper開始工作 … 等等!停車!停車!爲啥這麼眼熟呢? 嗯哼,不可說,不可說~~~

參考資料

  1. Dart asynchronous programming: Isolates and event loops

  2. Futures - Isolates - Event Loop

  3. Android單線程模型和JavaScript單線程模型

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章