Flutter-Dart中的非同步和多執行緒講解

語言: CN / TW / HK

眾所周知,Dart是一門單執行緒的語言,我們可以將一些耗時的任務放到非同步操作中,但是非同步任務必須等執行緒空閒時才會去執行,這是無法滿足有些場景需求的,下面就來講下如何處理這些場景。

如何處理耗時的操作

不同語言的不同處理方式

  • 多執行緒。比如 Java、C++,就是開啟一個新的執行緒,將耗時操作放在新的執行緒裡面處理,再通過執行緒間通訊的方式,將拿到的資料傳給主執行緒處理。
  • 單執行緒+事件迴圈。比如 JavaScript、Dart 都是基於單執行緒加事件迴圈來完成耗時操作的處理。

單執行緒的非同步操作

應用程式大部分時間是處於空閒狀態的,並不是一直在和使用者進行互動。而我們的作業系統存在阻塞式呼叫非阻塞式呼叫

  • 阻塞式呼叫:呼叫結果返回之前,當前執行緒會被掛起,呼叫執行緒只有在得到呼叫結果之後才會繼續執行。
  • 非阻塞式呼叫:呼叫執行之後,當前執行緒不會停止執行,只需要間隔一段時間來檢查一下有沒有結果返回即可。

Dart 的非同步操作就是利用非阻塞式呼叫實現的。

什麼是事件迴圈

和 iOS 應用很像,在 Dart 的執行緒中也存在事件迴圈和訊息佇列的概念,但在 Dart 中執行緒叫做isolate。應用程式啟動後,開始執行 main 函式並執行 main isolate

每個 isolate 包含一個事件迴圈以及兩個事件佇列,event loop事件迴圈,以及event queuemicrotask queue事件佇列,event 和 microtask 佇列有點類似 iOS 的 source0 和source1。

  • event queue:負責處理I/O事件、繪製事件、手勢事件、接收其他 isolate 訊息等外部事件。

  • microtask queue:可以自己向 isolate 內部新增事件,事件的優先順序比 event queue高。

Dart 中的非同步

Dart中的非同步操作主要使用Future以及asyncawait,async 和 await 是要一起使用的,這就是協程的一個語法糖。

  • Future 延時操作的一個封裝,可以將非同步任務封裝為Future物件,我們通常通過then()來處理返回的結果
  • async 用於標明函式是一個非同步函式,其返回值型別是Future型別
  • await 用來等待耗時操作的返回結果,這個操作會阻塞到後面的任務

什麼是協程

協程分為無線協程有線協程,無線協程在離開當前呼叫位置時,會將當前變數放在堆區,當再次回到當前位置時,還會繼續從堆區中獲取到變數。所以,一般在執行當前函式時就會將變數直接分配到堆區,而asyncawait就屬於無線協程的一種。有線協程則會將變數繼續儲存在棧區,在回到指標指向的離開位置時,會繼續從棧中取出呼叫。

async、await原理

以 async、await為例,協程在執行時,執行到async則表示進入一個協程,會同步執行async的程式碼塊。async的程式碼塊本質上也相當於一個函式,並且有自己的上下文環境。當執行到await時,則表示有任務需要等待,CPU 則去排程執行其他 IO,也就是後面的程式碼或其他協程程式碼。過一段時間 CPU 就會輪循一次,看某個協程是否任務已經處理完成,有返回結果可以被繼續執行,如果可以被繼續執行的話,則會沿著上次離開時指標指向的位置繼續執行,也就是await標誌的位置。

由於並沒有開啟新的執行緒,只是進行 IO 中斷改變 CPU 排程,所以網路請求這樣的非同步操作可以使用asyncawait,但如果是執行大量耗時同步操作的話,應該使用isolate開闢新的執行緒去執行。

下面舉例來講解非同步

  • 模擬一個同步的耗時操作,看會輸出怎樣的結果

截圖2021-12-06 下午9.30.18.png

輸出結果,C 並沒有因為有耗時操作而影響執行緒的任務執行

flutter: B flutter: C flutter: A flutter: D

  • 那現在對這個例子改造一下,加上 async、await

截圖2021-12-06 下午9.37.14.png

輸出結果是,C 等待了耗時操作完成之後才執行。使用 async 來標明 getData 這個函式是一個非同步函式,await 用於等待請求返回的結果,此時會阻塞掉後面的程式碼,只有當請求結束後面的程式碼才會執行。

flutter: B flutter: A flutter: D flutter: C

  • 多Future 情況下執行順序是什麼樣的

截圖2021-12-06 下午9.42.29.png

執行的順序是按著建立順序執行

flutter: A flutter: B flutter: C flutter: D

  • Future 是鏈式呼叫的,可以在後面呼叫

dart //處理返回結果 .then((value) => null) //處理錯誤 .onError((error, stackTrace) => null) //完成回撥 .whenComplete(() => null) //處理異常 .catchError(onError);

Dart 中的事件迴圈

  • 微任務佇列:表示一個短時間內就會完成的非同步任務,它的優先順序比事件佇列高。

  • 事件佇列:包含所有的外來事件,比如:I/O、手勢、繪圖等。

這是一張 Flutter 任務佇列的執行圖:

截圖2021-12-07 下午9.20.41.png

這兩個佇列也是有優先順序的,當 isolate 開始執行後,會先處理 microtask 的事件,當microtask 佇列中沒有事件後,才會處理 event佇列中的事件,並按照這個順序反覆執行。但需要注意的是,當執行 microtask 事件時,會阻塞 event 佇列的事件執行,這樣就會導致渲染、手勢響應等 event 事件響應延時。為了保證渲染和手勢響應,應該儘量將耗時操作放在 event 佇列中。

下面這個例子可以證明這一點:

截圖2021-12-06 下午9.59.19.png

flutter: 開始執行 flutter: 結束執行 flutter: 微任務 flutter: A flutter: A結束 flutter: B flutter: B結束

假如微任務新增在非同步任務裡面,非同步任務和微任務誰先執行呢?看下面這個例子:

截圖2021-12-07 下午9.28.31.png

執行結果是非同步任務裡面的微任務沒有非同步任務先執行,並且非同步任務鏈式呼叫的處理也比微任務優先

flutter: 開始執行 flutter: 結束執行 flutter: 微任務 flutter: A flutter: A結束 flutter: A裡面的微任務

多執行緒

在一個頁面中做耗時比較大的運算時,就算用了 async、await 非同步處理,UI頁面的動畫還是會卡頓,因為還是在這個UI執行緒中做運算,非同步只是你可以先做其他,等我這邊有結果再返回,但是,我們的計算仍舊是在這個UI執行緒,仍會阻塞UI的重新整理,非同步只是在同一個執行緒的併發操作。所以這個時候就需要建立新的執行緒來執行耗時操作解決這個問題。

什麼是 Isolate

Isolate 是 Dart 平臺對執行緒的實現方案,但和普通 Thread 不同的是,isolate 擁有獨立的記憶體,isolate 由執行緒和獨立記憶體構成。正是由於 isolate 執行緒之間的記憶體不共享,所以 isolate 執行緒之間並不存在資源搶奪的問題,所以也不需要鎖。通過 isolate 可以很好的利用多核 CPU,來進行大量耗時任務的處理。

但是12月28號,Google釋出了Dart2.15版本。我們首先重新設計和實現了 isolate 的工作方式,引入了一個新概念: isolate 組。Isolate 組中的 isolate 共享各種內部資料結構,這些資料結構則表示正在執行的程式。這使得組中的單個 isolate 變得更加輕便。如今,因為不需要初始化程式結構,在現有 isolate 組中啟動額外的 isolate 比之前快 100 多倍,並且產生的 isolate 所消耗的記憶體減少了 10 至 100 倍。關於Dart2.15版本更多的內容可以參考:https://mp.weixin.qq.com/s/g-1uCl3upI-JYHxUeEIbKg 。

先看下面這個非同步的例子,一看就知道執行順序是 A->B->C

截圖2021-12-06 下午10.13.03.png

下面舉例說明 Dart 中確認存在多執行緒

截圖2021-12-06 下午10.39.54.png

執行結果是這樣的,可以看出確實沒有按著建立的順序執行

flutter: A flutter: 第二個 flutter: 第二個 flutter: 第一個 flutter: 第二個 flutter: 第一個 flutter: 第二個 flutter: 第一個 flutter: 第一個 flutter: B

Isolate 通訊機制

isolate 執行緒之間的通訊主要通過 port 來進行,這個 port 訊息傳遞的過程是非同步的。通過建立通訊雙方的 sendPortreceiveport,進行相互的訊息傳遞。

截圖2021-12-06 下午10.41.50.png

這樣就實現了通訊。isolate實現方法需要用 static 修飾,不然會報下面這個錯誤

dart Unhandled Exception: Invalid argument(s): Isolate.spawn expects to be passed a static or top-level function

什麼是compute

dart 中的 Isolate 比較重量級,UI 執行緒和 Isolate 中的資料的傳輸比較複雜,因此 Flutter 為了簡化使用者程式碼,在 Foundation 庫中封裝了一個輕量級 compute 操作來實現。這個使用非常方便,並且可以直接返回值。

```dart import 'package:flutter/foundation.dart';

void computeTest() async { print('開始執行'); int b = await compute(test1, 10); print('結束執行: b = $b'); }

static int test1(int count) { sleep(Duration(seconds: 2)); print('執行方法'); return 100; } ```

使用場景

  • 任務執行事件很短的,比如幾十毫秒以內的建議用 Future
  • 任務執行時間長,只有一次返回的用compute,有多次返回的用Isolate

Mac Flutter環境配置及Android Studio的使用

Flutter-最全常用快捷鍵

參考資料:https://www.jianshu.com/p/54da18ed1a9e

歡迎關注、點贊及轉發。