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版本更多的內容可以參考:http://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的使用
參考資料:http://www.jianshu.com/p/54da18ed1a9e
歡迎關注、點贊及轉發。
- 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實現自動化打包
- Json自動解析生成Model工具
- iOS 腳本打包上傳蒲公英通知釘釘