Flutter-Dart中的非同步和多執行緒講解
眾所周知,Dart是一門單執行緒的語言,我們可以將一些耗時的任務放到非同步操作中,但是非同步任務必須等執行緒空閒時才會去執行,這是無法滿足有些場景需求的,下面就來講下如何處理這些場景。
如何處理耗時的操作
不同語言的不同處理方式
- 多執行緒。比如 Java、C++,就是開啟一個新的執行緒,將耗時操作放在新的執行緒裡面處理,再通過執行緒間通訊的方式,將拿到的資料傳給主執行緒處理。
- 單執行緒+事件迴圈。比如 JavaScript、Dart 都是基於單執行緒加事件迴圈來完成耗時操作的處理。
單執行緒的非同步操作
應用程式大部分時間是處於空閒狀態的,並不是一直在和使用者進行互動。而我們的作業系統存在阻塞式呼叫
和非阻塞式呼叫
。
- 阻塞式呼叫:呼叫結果返回之前,當前執行緒會被掛起,呼叫執行緒只有在得到呼叫結果之後才會繼續執行。
- 非阻塞式呼叫:呼叫執行之後,當前執行緒不會停止執行,只需要間隔一段時間來檢查一下有沒有結果返回即可。
Dart 的非同步操作就是利用非阻塞式呼叫實現的。
什麼是事件迴圈
和 iOS 應用很像,在 Dart 的執行緒中也存在事件迴圈和訊息佇列的概念,但在 Dart 中執行緒叫做isolate
。應用程式啟動後,開始執行 main 函式並執行 main isolate
。
每個 isolate 包含一個事件迴圈以及兩個事件佇列,event loop事件迴圈
,以及event queue
和microtask queue事件佇列
,event 和 microtask 佇列有點類似 iOS 的 source0 和source1。
-
event queue:負責處理I/O事件、繪製事件、手勢事件、接收其他 isolate 訊息等外部事件。
-
microtask queue:可以自己向 isolate 內部新增事件,事件的優先順序比 event queue高。
Dart 中的非同步
Dart中的非同步操作主要使用Future
以及async
、await
,async 和 await 是要一起使用的,這就是協程的一個語法糖。
- Future 延時操作的一個封裝,可以將非同步任務封裝為
Future
物件,我們通常通過then()來處理返回的結果 - async 用於標明函式是一個非同步函式,其返回值型別是Future型別
- await 用來等待耗時操作的返回結果,這個操作會阻塞到後面的任務
什麼是協程
協程分為無線協程
和有線協程
,無線協程在離開當前呼叫位置時,會將當前變數放在堆區,當再次回到當前位置時,還會繼續從堆區中獲取到變數。所以,一般在執行當前函式時就會將變數直接分配到堆區,而async
、await
就屬於無線協程的一種。有線協程則會將變數繼續儲存在棧區,在回到指標指向的離開位置時,會繼續從棧中取出呼叫。
async、await原理
以 async、await為例,協程在執行時,執行到async
則表示進入一個協程,會同步執行async
的程式碼塊。async
的程式碼塊本質上也相當於一個函式,並且有自己的上下文環境。當執行到await
時,則表示有任務需要等待,CPU 則去排程執行其他 IO,也就是後面的程式碼或其他協程程式碼。過一段時間 CPU 就會輪循一次,看某個協程是否任務已經處理完成,有返回結果可以被繼續執行,如果可以被繼續執行的話,則會沿著上次離開時指標指向的位置繼續執行,也就是await
標誌的位置。
由於並沒有開啟新的執行緒,只是進行 IO 中斷改變 CPU 排程,所以網路請求這樣的非同步操作可以使用async
、await
,但如果是執行大量耗時同步操作的話,應該使用isolate
開闢新的執行緒去執行。
下面舉例來講解非同步
- 模擬一個同步的耗時操作,看會輸出怎樣的結果
輸出結果,C 並沒有因為有耗時操作而影響執行緒的任務執行
flutter: B
flutter: C
flutter: A
flutter: D
- 那現在對這個例子改造一下,加上 async、await
輸出結果是,C 等待了耗時操作完成之後才執行。使用 async 來標明 getData 這個函式是一個非同步函式,await 用於等待請求返回的結果,此時會阻塞掉後面的程式碼,只有當請求結束後面的程式碼才會執行。
flutter: B
flutter: A
flutter: D
flutter: C
- 多Future 情況下執行順序是什麼樣的
執行的順序是按著建立順序執行
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 任務佇列的執行圖:
這兩個佇列也是有優先順序的,當 isolate 開始執行後,會先處理 microtask
的事件,當microtask 佇列中沒有事件後,才會處理 event佇列
中的事件,並按照這個順序反覆執行。但需要注意的是,當執行 microtask 事件時,會阻塞 event 佇列的事件執行,這樣就會導致渲染、手勢響應等 event 事件響應延時。為了保證渲染和手勢響應,應該儘量將耗時操作放在 event 佇列中。
下面這個例子可以證明這一點:
flutter: 開始執行
flutter: 結束執行
flutter: 微任務
flutter: A
flutter: A結束
flutter: B
flutter: B結束
假如微任務
新增在非同步任務
裡面,非同步任務和微任務誰先執行呢?看下面這個例子:
執行結果是非同步任務裡面的微任務沒有非同步任務先執行,並且非同步任務鏈式呼叫的處理也比微任務優先
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
下面舉例說明 Dart 中確認存在多執行緒
執行結果是這樣的,可以看出確實沒有按著建立的順序執行
flutter: A
flutter: 第二個
flutter: 第二個
flutter: 第一個
flutter: 第二個
flutter: 第一個
flutter: 第二個
flutter: 第一個
flutter: 第一個
flutter: B
Isolate 通訊機制
isolate 執行緒之間的通訊主要通過 port
來進行,這個 port 訊息傳遞的過程是非同步的。通過建立通訊雙方的 sendPort
和 receiveport
,進行相互的訊息傳遞。
這樣就實現了通訊。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的使用
參考資料:https://www.jianshu.com/p/54da18ed1a9e
歡迎關注、點贊及轉發。
- Flutter:仿京東專案實戰(4)-購物車頁面功能實現
- Flutter整合原生遇到的問題彙總
- Flutter:仿京東專案實戰(3)-商品詳情頁功能實現
- Flutter-Dart中的非同步和多執行緒講解
- iOS-底層原理分析之Block本質
- Flutter-官方推薦的Flutter與原生互動外掛Pigeon
- Flutter-flutter_sound錄音與播放
- iOS-CocoaPods的原理及Podfile.lock問題
- iOS配置多環境的三種方案
- iOS-各種Crash防護
- iOS-Swift中常見的幾種閉包
- Flutter:仿京東專案實戰(2)-分類和商品列表頁面功能實現
- Flutter:仿京東專案實戰(1)-首頁功能實現
- Flutter-JSON轉Model的四種便捷方案
- Flutter-導航與路由堆疊詳解
- Flutter 與原生通訊的三種方式
- iOS-記憶體洩漏檢測
- Fastlane實現自動打包
- 懶人必備神器-Xcode程式碼塊
- Jenkins實現自動化打包