Dart
中的事件循環是單線程的,在流暢性與安全性體驗較好,核心分爲主線程、微任務、宏任務。主線程主要包括業務處理、網絡IO、本地文件IO、異步等事件。dart的單線程中有兩個事件隊列,一個是微任務隊列、一個是事件隊列。git
微任務隊列包含有 Dart 內部的微任務,主要是經過 scheduleMicrotask 來調度。github
事件隊列包含外部事件,例如 I/O 、 Timer ,繪製事件等等。web
若是你的代碼具備依賴型,最好是顯式的。這有助於開發人員理解您的代碼並使您的代碼更加健壯。安全
想給事件隊列中鏈式添加任務的錯誤操做示範:bash
future.then(...set an important variable...);
Timer.run(() {...use the important variable...});
複製代碼
正確的是:markdown
future.then(...set an important variable...)
.then((_) {...use the important variable...});
複製代碼
在Dart
單線程中,他們的結果都是一致的,可是理解起來難易程度不是一致的。網絡
當你想稍後在任務中執行你的代碼,可使用Future
類,它將把任務添加到事件隊列末尾,或者使用頂級函數scheduleMicrotask
把任務添加到微任務隊列的末尾。多線程
爲了更好的使用then
,或者在發生錯誤時也須要執行的話,那麼請在whenComplate()
代替then
.併發
可使用Timer
或者Future
均可以異步
您也可使用Timer計劃任務,可是若是任務中發生任何未捕獲的異常,則應用程序將退出。相反,咱們建議使用Future,它創建在Timer之上,並增長了諸如檢測任務完成和對錯誤進行響應的功能。
添加代碼到事件隊列
new Future(() { // ...code goes here... }); 複製代碼
你可使用then
或者whenComplate
執行後邊的任務,看下這個例子
new Future(() => 21) .then((v) => v*2) .then((v) => print(v)); 複製代碼
若是你想稍後再執行的話,請使用Future.delayed()
:
new Future.delayed(const Duration(seconds:1), () { // ...code goes here... }); 複製代碼
瞭解了基本的用法以後,那麼如何把他們搭配使用呢?
有了單線程和隊列,那必然有對應的循環,這樣子才能執行不一樣的隊列任務和處理事件,那麼咱們看下 循環。
main
函數,併產生相應的微任務和事件隊列驗證一下上邊的運行原理,咱們看下下邊的代碼:
void main() { test(); } /// 微任務 /// 定時器 void test() async { print('start'); scheduleMicrotask(() { print('Microtask 1'); }); Future.delayed(Duration(seconds: 0)).then((value) { print('Future 1'); }); Timer.run(() { print('Timer 1'); }); print('end'); } 複製代碼
運行過程以下:
main
函數,打印start
scheduleMicrotask
微任務,添加任務到微任務隊列中Future
事件,給事件隊列添加任務timer
事件,給事件隊列添加任務end
Microtask 1
Future
任務,執行打印Future 1
Timer 1
任務,執行打印Timer1
輸出
start
end
Microtask 1
Future 1
Timer 1
複製代碼
看下面這個例子證實Timer
和Future
是一個類型的事件。
/// 微任務 /// 定時器 void test2() async { print('start'); scheduleMicrotask(() { print('Microtask 1'); }); Timer.run(() { print('Timer 1'); }); Future.delayed(Duration(seconds: 0)).then((value) { print('Future 1'); }); print('end'); } 複製代碼
輸出
start
end
Microtask 1
Timer 1
Future 1
複製代碼
上面的test
和test2
中Timer
和Future
位置調換了一下,也就是添加事件任務前後順序顛倒了一下,在執行的時候也顛倒了一下。咱們再看Future
源碼:
factory Future.delayed(Duration duration, [FutureOr<T> computation()]) { _Future<T> result = new _Future<T>(); new Timer(duration, () { if (computation == null) { result._complete(null); } else { try { result._complete(computation()); } catch (e, s) { _completeWithErrorCallback(result, e, s); } } }); return result; } 複製代碼
Future.delayed
源碼本質就是將任務添加到Timer
中,在指定時間後運行該任務。
微任務隊列優先級高於事件隊列,因此每次執行任務首先判斷是否存在未執行的微任務。
事件隊列執行完,有可能有新的微任務被添加到隊列中,因此還須要掃描微任務隊列一次。 看下面的例子:
void test3() async { print('start'); //1 scheduleMicrotask(() {//2 print('Microtask 1');//5 }); Timer.run(() {//3 print('Timer 1');//6 Timer.run(() {//9 print('Timer 1 Microtask 2 ');//10 }); scheduleMicrotask(() {//7 print('Microtask 2');//8 }); }); print('end');//4 } 複製代碼
執行順序:
start
Timer 1
到事件隊列end
任務Microtask 1
,當即執行Timer 1
任務並執行,添加事件任務Timer 1 Microtask 2
,添加微任務Microtask 2
到微任務隊列Microtask 2
,並執行。Timer 1 Microtask 2
並執行。結果輸出
start
end
Microtask 1
Timer 1
Microtask 2
Timer 1 Microtask 2
複製代碼
根據前邊的瞭解,執行完微任務再執行事件任務,當某個事件處理時間須要很長,則後邊的任務則會一直處於等待狀態。下邊咱們看一個例子,當任務足夠都,仍是須要必定時間去處理的。
void test4() async { print('start ${DateTime.now()}'); for(int i =0;i < 99999;i++){ scheduleMicrotask(() { print('Microtask 1'); }); } Timer.run(() { print('Timer 1 ${DateTime.now()}'); }); print('end ${DateTime.now()}'); } 複製代碼
輸出:
start 2020-07-28 17:44:11.561886
end 2020-07-28 17:44:11.593989
...
Microtask 1
.....
Timer 1 2020-07-28 17:44:11.893093
複製代碼
能夠看出這些任務執行完成耗時基本達到了0.33
秒。
當一個線程出現處理任務不夠了,那麼就須要在開啓一個線程了。
上邊的 dart
進入main函數是單線程的,在Dart中,多線程叫作Isolates
線程,每一個Isolates線程不共享內存,經過消息機制通訊。
咱們看個例子,利用Dart
的Isolates
實現多線程。
void test5()async{ final rece = ReceivePort(); isolate = await Isolate.spawn(sendPort, rece.sendPort); rece.listen((data){ print('收到了 ${data} ,name:$name'); }); } void sendPort(SendPort sendPort){ sendPort.send('發送消息'); } Isolate isolate; String name='fgyong'; void main() { test5(); } 複製代碼
輸出
收到了 發送消息 ,name:fgyong
複製代碼
多線程相互溝通怎麼處理?
建立線程以後子線程須要發送主線程一個端口和消息,主線程記錄該端口,下次和子線程通信使用該端口便可。
具體代碼以下:
/// 新線程執行新的任務 並監聽 Isolate isolate; Isolate isolate2; void createTask() async { ReceivePort receivePort = ReceivePort(); isolate = await Isolate.spawn(sendP1, receivePort.sendPort); receivePort.listen((data) { print(data); if (data is List) { SendPort subSencPort = (data as List)[1]; String msg = (data as List)[0]; print('$msg 在主線程收到'); if (msg == 'close') { receivePort.close(); } else if (msg == 'task') { taskMain(); } subSencPort.send(['主線程發出']); } }); } void sendP1(SendPort sendPort) async { ReceivePort receivePort = new ReceivePort(); receivePort.listen((data) async { print(data); if (data is List) { String msg = (data as List)[0]; print('$msg 在子線程收到'); if (msg == 'close') { receivePort.close(); } else if (msg == 'task') { var m = await task(); sendPort.send(['$m', receivePort.sendPort]); } } }); sendPort.send(['子線程線程發出', receivePort.sendPort]); } Future<String> task() async { print('子線程執行task'); for (var i = 0; i < 99999999; i++) {} return 'task 完成'; } void taskMain() { print('主線程執行task'); } 複製代碼
輸出:
[子線程線程發出, SendPort]
子線程線程發出 在主線程收到
[主線程發出]
主線程發出 在子線程收到
複製代碼
更多子線程與主線程交互請上代碼庫查看
假設一個項目,須要 2 個團隊去完成,團隊中包含多項任務。能夠分爲 2 個高優先級任務(高優先級的其中,會產生2個任務,一個是緊急一個是不緊急),和 2 個非高優先級任務(非高優先級的其中,會產生有 2 個任務,一個是緊急一個是不緊急)。其中還有一個是必須依賴其餘團隊去作的,由於本團隊沒有那方面的資源,第三方也會產生一個高優先級任務和一個低優先級任務。
根據緊急任務做爲微任務,非緊急任務做爲事件任務來安排,第三方是新開線程
主任務 | 高優先級(微任務) | 低優先級(事件任務) | 第三方(Isolate) |
---|---|---|---|
H1 | H1-1 | L1-2 | 否 |
H2 | H2-1 | L2-2 | 否 |
L3 | H3-1 | L3-2 | 否 |
L4 | H4-1 | L4-2 | 否 |
I5 | IH5-1 | I5-2 | 是 |
void test6() { createTask();//建立線程 scheduleMicrotask(() {//第一個微任務 print('H1'); scheduleMicrotask(() {//第一個緊急任務 print('H1-1'); }); Timer.run(() {//第一個非緊急任務 print('L1-2'); }); }); scheduleMicrotask(() {// 第二個高優先級任務 print('H2'); scheduleMicrotask(() {//第二個緊急任務 print('H2-1'); }); Timer.run(() {//第二個非緊急任務 print('L2-2'); }); }); Timer.run(() {// 第一個低優先級任務 print('L3'); scheduleMicrotask(() {//第三個緊急任務 print('H3-1'); }); Timer.run(() {//第三個非緊急任務 print('L3-2'); }); }); Timer.run(() {// 第二個低優先級任務 print('L4'); scheduleMicrotask(() {//第四個緊急任務 print('H4-1'); }); Timer.run(() {//第四個非緊急任務 print('L4-2'); }); }); } /// 新線程執行新的任務 並監聽 Isolate isolate; void createTask() async { ReceivePort receivePort = ReceivePort(); isolate = await Isolate.spawn(sendPort, receivePort.sendPort); receivePort.listen((data) { print(data); }); } /// 新線程執行任務 void sendPort(SendPort sendPort) { scheduleMicrotask(() { print('IH5-1'); }); Timer.run(() { print('IL5-2'); }); sendPort.send('第三方執行任務結束'); } 複製代碼
運行結果
H1
H2
H1-1
H2-1
L3
H3-1
L4
H4-1
L1-2
L2-2
L3-2
L4-2
IH5-1
IL5-2
第三方執行任務結束
複製代碼
能夠看到H
開頭的爲高優先級,L
開頭爲低優先級,基本高優先級運行都在低優先級以前,符合預期。
因爲建立線程須要事件,其餘任務均爲耗時過短,那麼咱們從新作一個耗時事件長的任務便可。
createTask();//建立線程 for (var i = 0; i < 9999999999; i++) { } ... 複製代碼
輸出:
IH5-1
IL5-2
H1
H2
H1-1
H2-1
第三方執行任務結束
L3
H3-1
L4
H4-1
L1-2
L2-2
L3-2
L4-2
複製代碼
由於這個事件屬於低優先級,而H
開頭的都是高優先級任務。
在L3
上邊。而不是在最下邊,必定在低優先級隊列的第一個嗎 ?
因爲開始的耗時操做事件太長,致使全部任務執行前,第三方任務已經執行完成,因此 第三方執行任務結束 是第一個添加到低優先級任務隊列的,因此在低優先級隊列第一個執行。
當耗時操做比較少時,則 第三方執行任務結束 添加順序則不肯定。
Dart
中異步和多線程是分開的,異步只是事件循環中的多事件輪訓的結果,而多線程能夠理解爲真正的併發(多個線程同時作事)。在單個線程中,又分爲微任務和其餘事件隊列,微任務隊列優先級高於其餘事件隊列。