什麼是isolate
dart 雖然是一個單線程語言 但是不代表他不支持多線程併發
- 在dart中線程不叫線程叫做isolate(隔離區)所有的代碼都運行在這
- 類似於線程但不共享內存的獨立工作程序,僅通過消息進行通信。
- 每個isolate 都有一個完整的事件循環機制,每個隔離區都有自己的內存堆,確保每個隔離區的狀態都不會被其他隔離區訪問。
- 這意味着在一個 Isolate 中運行的代碼與另外一個 Isolate不存在任何關聯。
- 依賴這點所以我們通過Isolate實現併發
名詞解釋
- 在下面描述中
mainIsolate
代表調用Isolate或者叫主Isolate
newIsolate
代表**新創建的Isolate
**mainReceivePort
代表**調用Isolate
的ReceivePort
**newReceivePort
代表**新Isolate
的ReceivePort
**mainSendPort
代表**調用Isolate
的SendPort
**newSendPort
代表**新Isolate
的SendPort
**
實現Isolate
在
mainIsolate
中創建newIsolate
有兩種方法spawn
,spawnUri
創建步驟
- 創建
newIsolate
並與當前調用mainIsolate
並通過sendPort
建立連接
- 因爲
Isolate
之間不共享內存且通過消息交互,所以**Isolate
之間通訊需要持有對方發送消息的端口,文檔裏叫**SendPort
- 對於
sendPort
文檔裏這樣描述:[SendPort]s are created from [ReceivePort]s.- 翻譯:發送端口是從接收端口創建的
RecivePort
文檔裏這樣描述:Together with SendPort, the only means of communication between isolates.- 與發送端一起,是一種
isolate
之間的通訊方式- 所以
Isolate
之間通訊需要持有對方的SendPort
- 創建
newIsolate
- 獲得
mainIsolate
的mainRecivePort
和mainSendPort
- 要將
mainIsolate
的mainSendPort
發送到newIsolate
mainRecivePort
監聽mainSendPort
發送回來的數據- 至於如何將
mainSendPort
發送到newIsolate
的請看下面的例子- 發送數據
- 通過各自的sendPort.send
- 接收數據通過
- 各自的RecivePort
- 在合適的機會銷燬Isolate
下面看具體的步驟
spawn
-
spawn創建並生成與當前Isolate共享相同代碼的Isolate。
-
Future<Isolate> spawn<T>(void entryPoint(T message), T message)
- 可以看到每一個isolate 需要兩個必要參數
- 入口點函數
entryPoint
- 文檔對其這樣描述
- 參數
entryPoint
指定要在派生的隔離中調用的初始函數, - 入口點函數在新的Isolate中被調用,
message
只有[message]作爲entryPoint
的參數。message
通常用於傳送調用Isolate
的SendPort
對象
- 入口點函數
- 即
entryPoint
函數就是要在**newIsolate
**中運行的函數 - 因爲Isolate相互通訊需要持有對方的
sendport
- 所以我們將**
mainIsolate
的mainSendPort
** 作爲**message
傳遞到將要在newIsolate
** 中執行的入口函數(entryPoint
)中,使newIsolate
持有mainSendPort
- 入口函數在**
newIsolate
** 中執行,我們再通過**mainSendPort
** 將**NewIsolate
的newSendPort
** 發送到**mainIsolate
**中 ,使mainIsolate
持有newSendPort
- 這樣**
mainIsolate
持有了newSendPort
** ,- 這樣**
mainIsolate
通過newSendPort
** 可以將**mainIsolate
中的消息發送到newIsolate
**
- 這樣**
- 這樣**
newIsolate
持有了mainSendPort
**- 這樣**
newIsolate
通過mainSendPort
** 可以將**newIsolate
中的消息發送到mainIsolate
**
- 這樣**
- 所以是通過入口函數將**
主Isolate的SendPort
發送到Newisolate
**
- 所以我們將**
- 需要注意 isolate 的「入口函數(
entryPoint
)」必須是頂級函數或靜態方法。
- 可以看到每一個isolate 需要兩個必要參數
-
示例代碼
-
import 'dart:io'; import 'dart:isolate'; main() async { print("main start"); createIsolate(); print("main end"); } Isolate newIsolate; void createIsolate() async { // 兩個Isolate要想互相通訊須持有對方的的sendPort // 獲取mainIsolate的監聽器 mainReceivePort ReceivePort main_rp = ReceivePort(); // 獲取 mainIsolate 的 SendPort 並作爲參數傳遞給newIsolate // 使 newIsolate 持有 mainSendPort,用於通訊 // 使 newIsolate 可以通過 mainSendPort 將 newIsolate 的發送消息回 mainIsolate SendPort main_send = main_rp.sendPort; // 創建新的isolate newIsolate = await Isolate.spawn(excuter, main_send); // 這裏需要得到 newIsolate 的 SendPort, // 讓 mainIsolate 持有 newSendPort,用於通訊 // 使 mainIsolate 可以通過 newSendPort 將 mainIsolate 的發送消息回 newIsolate // 注意 這裏 newSendPort 是 newIsolate中的mainSendPort 發送回來的所以要在監聽中獲取newSendPort SendPort new_send; //主接收器(mainReceivePort)開始監聽newIsolate中的mainSendPort發送回來的消息 main_rp.listen((message) { print("NewIsolat通過main_send發送來一條消息 $message ,到主Isolate"); if (message[0] == 0) { // 獲取newSendPort new_send = message[1] as SendPort; } else { new_send?.send("mian_isolate 通過new_send發送了一條消息到NewIsolate"); } }); } // 入口函數將在newIsolate中執行 void excuter(SendPort mainSendPort) { // 獲取newIsolate的監聽器newReceivePort ReceivePort new_rp = ReceivePort(); //newReceivePort開始監聽 mainIsolate中的newSendPort發送回來的消息 new_rp.listen((message) { print(message); // 接收到第一條main發送過來的函數 就銷燬newIsolate print("銷燬NewIsolate"); destroyNewIsolate(); }); // 獲取newIsolate的 SendPort SendPort new_send = new_rp.sendPort; //將其發送到 mainIsolate // 讓 mainIsolate 持有 newSendPort,用於通訊 // 使 mainIsolate 可以通過 newSendPort 將 mainIsolate 的發送消息回 newIsolate mainSendPort.send([0, new_send]); // 模擬耗時5秒 sleep(Duration(seconds: 5)); mainSendPort.send([1, "excuter 任務完成"]); print("NewIsolat 執行結束"); } //銷燬newIsolate destroyNewIsolate() { // 任務執行結束銷燬newIsolate newIsolate?.kill(priority: Isolate.immediate); newIsolate = null; } /* 輸出 newIsolat通過main_send發送來一條消息 [0, SendPort] ,到mainIsolate newIsolat通過main_send發送來一條消息 [1, excuter 任務完成] ,到mainIsolate NewIsolat 執行結束 mianIsolate 通過new_send發送了一條消息到newIsolate 銷燬NewIsolate */
spawn Uri
-
創建並派生一個Isolate,該Isolate使用指定的URI從庫中運行代碼。
-
Isolate.spawnUri(Uri uri,List<String> args,var message)
;` -
spawnUri
方法有三個必須的參數,- 第一個是Uri,指定一個新Isolate代碼文件的路徑,
- 第二個是參數列表,類型是List,
- 第三個是消息。其實是發送消息的端口
SendPort
-
注意這種方式
-
newIsolate
代碼在一個單獨的文件裏 -
newIsolate
的的執行函數,必須包是一個main函數,它是
newIsolate的入口方法,
-
main
函數必須可以用零個一個或者兩個參數調用-
`main()` `main(args)` `main(args, message)`
-
該
main
函數中的args
參數列表,正對應spawnUri
中的第二個參數。如不需要向newIsolate
中傳參數,該參數可傳空List -
message則是調用Isolate的SendPort
-
-
-
示例代碼
-
mainIsolate
-
import 'dart:isolate'; Isolate newIsolate; main() async { ReceivePort mainReceivePort = ReceivePort(); SendPort mainSendPort = mainReceivePort.sendPort; List<String> list = ["hello, isolate", "this is args"]; var uri = Uri(path: "./newTaskUri.dart"); // 創建newIsolate 並建立連接 newIsolate = await Isolate.spawnUri(uri, list, mainSendPort); // 需要獲取 newSendPort 用於通訊 // newSendPort 是 newIsolate中的mainSendPort 發送回來的所以要在監聽中獲取結果 SendPort newSendPort; mainReceivePort.listen((message) { print("newIsolat通過main_send發送來一條消息 $message ,到mainIsolate"); if ("excuter 任務完成" == message[1]) { // 銷燬newIsolate print("銷燬newIsolate"); destroyNewIsolate(); } if (message[0] == 0) { // 獲取newSendPort newSendPort = message[1] as SendPort; } else { newSendPort?.send("mian_isolate 通過new_send發送了一條消息到newIsolate"); } }); } //銷燬newIsolate destroyNewIsolate() { // 任務執行結束銷燬newIsolate newIsolate?.kill(priority: Isolate.immediate); newIsolate = null; }
-
-
newIsolate
文件newTaskUri.dart
-
import 'dart:io'; import 'dart:isolate'; // 這裏的main 就是入口函數 在newIsolate中執行 // 就相當與 spawn中的 excuter // 內部執行回傳sendport,消息監聽發送,邏輯是一樣的 // 區別就是多了一個參數列表可以傳一些參數處理些邏輯 功能更豐富了 void main(args, SendPort mainSendPort) { try { print("newIsolate 開始"); print("newIsolate (參數列表)args: $args"); ReceivePort newRecivePort = new ReceivePort(); //newReceivePort開始監聽 newSendPort發送回來的消息 newRecivePort.listen((message) { print(message); // 接收到第一條消息 }); // 獲取newSendPort 並通過mainSendPort 回傳到mainIsolate SendPort newSendPort = newRecivePort.sendPort; mainSendPort.send([0, newSendPort]); // 模擬耗時5秒 sleep(Duration(seconds: 5)); mainSendPort.send([1, "excuter 任務完成"]); print("NewIsolat 執行結束"); } catch (e) { print("myerr $e"); } }
-
-
輸出結果
-
/** newIsolate 開始 newIsolate (參數列表)args: [hello, isolate, this is args] newIsolat通過main_send發送來一條消息 [0, SendPort] ,到mainIsolate NewIsolat 執行結束 newIsolat通過main_send發送來一條消息 [1, excuter 任務完成] ,到mainIsolate 銷燬newIsolate */
-
與
spawn
輸出結果對比少了一個mainIsolat
向newIsolate
發送消息,是因爲代碼中銷燬newIsolate
時機不同spawn
在newIsolate
執行結束後mainIsolate
向newIsolate
發送消息且被處理後銷燬newIsolate
spawnUri
在newIsolate
執行結束後就銷燬了newIsolate
-
-
需要注意
-
無論是上面的**
spawn
還是spawnUri
**,運行後都會創建兩個Isolate- spawn創建並生成與當前Isolate共享相同代碼的新Isolate。
- spawnUri 一個獨立的不與調用Isolate共享代碼的新Isolate
-
想要相互通訊就必須持有對方的
SendPort
newIsoalte
要持有mainSendPort
靠創建Isolate
是入口點函數傳參 參數即`mainSendPort``- ``mainIsolate
要持有
newSendPort靠傳入
newIsolate的
mainSendPort發送到
mainIsolate`
-
釋放
newIsolate
- 當我們使用完自己創建的Isolate之後,最好調用
kill
將Isolate殺死,否則Isolate 會一直存在造成內存消耗
- 當我們使用完自己創建的Isolate之後,最好調用
-
Platform-Channel 通信僅僅由主 isolate 支持。該主 isolate 對應於應用啓動時創建的 isolate。
- 也就是說,通過編程創建的 isolate 實例,無法實現 Platform-Channel 通信 這難道是
區別
-
swpanUri
的newIsolate
必須在單獨的文件裏,又因爲必須有main函數作爲入口,所以程序會出現兩個main -
在flutter環境下一直又一個main的錯誤未處理 我猜測可能是因爲兩個main函數引起的,也有可能是上面第四點原因導致的如果有人知道請評論告訴我下 謝謝了
-
spawn
通常我們newIsolate
和mainIsolate
寫在同一個文件,也不會出現兩個main函數方便管理Isolate -
在flutter環境下
spwan
創建可以正常執行spwan
要注意入口點函數必須是頂層函數或者靜態函數
在flutter中創建Isolate
可以看到 無論是實現上述哪一種isolate 代碼數量都是比較繁瑣的
對此Flutter提供了一個函數**
compute
** 該函數封裝了通過spawn
實現Isolate
的代碼避免我們去寫通過
spawn
創建Isolate的一系列代碼,直接通過compute函數,這樣讓我們的代碼看起來更簡潔了
compute
如果任務只是進行一次計算返回結果,不需要雙端多次溝通的話 使用compute 函數將非常簡單
compute<Q, R>(ComputeCallback<Q, R> callback, Q message)
-
compute函數有兩個必須的參數
- callback 爲執行函數
- 必須是頂級函數或者靜態方法
- message爲消息 可以是callback執行函數的參數
- callback 爲執行函數
-
示例
-
import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'ComputeIsolate', theme: new ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar(title: Text("ComputeIsolate")), body: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Center( child: FlatButton( onPressed: () async { String s = await compute(work, 4); print(s); }, child: Text( "Click", )), ), Center( child: Text( "Click", )), ]))); } } String work(num duration) { print("work start"); sleep(Duration(seconds: duration)); return "$duration 秒後執行結束"; } // 運行點擊屏幕 後輸出 // work start // 4 秒後執行結束
-
-
怎麼樣 是不是很簡單
我們應該什麼時候使用Future和Isolate
- 耗時較多的任務放到Isolate中
- 如果你不想你的Ui卡頓或者程序中斷
- 通過之前的時間循環機制我們瞭解到Future只是將任務放到了Event隊列,還是在當前Isolate 不過是等其他代碼執行結束在執行Event隊列中的任務,而UI渲染交互等又都是在Event隊列中處理,如果我們Future任務耗時過多會導致Ui卡頓甚至整個進程都被中斷,所以我們才需要多Isolate 併發處理任務
- 直觀的說可以根據任務執行時間長短來區分
- 代碼段運行時間只要幾十毫秒=>Future
- 代碼段運行時間要幾百毫秒甚至更久應該用Isolate
- 比如Io操作
- 如果你不想你的Ui卡頓或者程序中斷
總結
- Isolate雖然可以併發但是也要考慮適用場景
- 如果需要使用Isolate 要考慮場景
- 只執行一次返回結果 不需要多次通訊 使用compute函數
- 如果需要多次溝通我們可以通過 spawn 來創建Isolate