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

歡迎關注、點贊及轉發。