前言
Dart作為Flutter的開發語言,瞭解Dart的機制是必可少的。本篇文章就介紹一下Dart的非同步操作與事件迴圈機制。
非同步操作我們都知道在開發過程中,如果有耗時操作,我們一般都會使用非同步任務解決,以防主執行緒卡頓。
事件迴圈是Dart中處理事件的一種機制。Flutter中就是通過事件迴圈來驅動程式的執行,這點與Android中的Handler有點類似。
Dart的事件迴圈機制
Dart語言是單執行緒模型的語言。這也就意味著Dart在同一時刻只能執行一個操作,其他操作在這個操作之後執行。那麼Dart的其他操作如何執行呢?在Dart中通過Event Loop來驅動事件的執行。
- Dart程式啟動時會建立兩個佇列,分別是MicroTak佇列(微服務佇列)和Event佇列(事件佇列);
- 然後執行main()方法;
- 最後啟動事件迴圈;
上面的圖片描述了Dart的事件迴圈機制。可以看到,Dart執行事件的流程是:
- 執行main();
- 執行MicroTask佇列中事件;
- 執行事件佇列中的事件;
可以看到,main()方法的優先順序最高,執行結束後,才執行兩個佇列中事件。其中MicroTask佇列的優先順序又高於Event佇列的優先順序。
MicroTask佇列和Event佇列
MicroTask 佇列適用於非常簡短且需要非同步執行的內部動作或者想要在Event執行前做某些操作,因為在MicroTask執行完以後,還需要執行Event佇列中事件。
在使用Dart時非同步事件應該優先考慮使用Event佇列。
Dart中的非同步
Future
Future 是一個非同步執行並且在未來的某一個時刻完成(或失敗)的任務。每一個Future都會執行一個事件並且將事件加入到Event佇列中去。
factory Future(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
Timer.run(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
複製程式碼
從上面Future的建構函式程式碼可以看到,通過Timer的run()方法將運算過程傳送到事件佇列中去,然後通過_complete()方法獲取了提交給Future的運算結果,最終返回一個Future物件。
void main(){
print('Before the Future');
Future((){
print('Running the Future');
}).then((_){
print('Future is complete');
});
print('After the Future');
Future<String>.microtask(() {
return "microtask";
}).then((String value) {
print(value);
});
}
複製程式碼
以上程式碼的執行結果是:
Before the Future
After the Future
microtask
Running the Future
Future is complete
複製程式碼
可以看到,這裡先執行的是main()方法中的列印資訊,然後列印了microtask,最後才執行了Future。這裡也可以證明Future是在事件佇列中執行的。
async、await關鍵字
async與await關鍵字配合使用可以更加優雅的處理dart中的非同步。對於有async修飾的函式來說,它會同步執行函式中的程式碼,直到遇到第一個await關鍵字,然後將await修飾的程式碼提交到事件佇列中處理,在await修飾的程式碼執行完畢後會立即執行剩下的程式碼,而剩下的程式碼將在微任務佇列中執行。
void methodAsync() async {
sleep(Duration(seconds: 1));
Future<String>(() {
return "future 1";
}).then((String value) {
print(value);
});
print("async start");
await microTaskAndEvent(); //輸出一個微任務列印 microtask 以及一個事件佇列任務列印 futrue
print("async end");
}
void main() {
methodAsync();
print("Hello World");
}
複製程式碼
以上程式碼的輸出結果是:
async start
Hello World
microtask
async end
future 1
future
複製程式碼
從列印結果可以看出,
- 這裡同步執行methodAsync()方法,即使在睡眠1s的情況下,也首先輸出了async start;然後輸出Hello World;
- 接下來執行了一個Future1,然後執行await關鍵字後面的函式;
- microTaskAndEvent()方法包括一個微任務和事件。這裡可以看到列印了microtask接著列印了async end,這裡可以證明async在await關鍵字之後的程式碼是放到微任務中執行的。
- 最後列印了事件佇列中的任務。
Isolate
Isolate從名稱上理解是獨立的意思。在dart中,Isolate有著自己的記憶體並且不存在通過共享記憶體的方式實現Isolate之間的通訊,同時Isolate是單執行緒的計算過程。
Isolate的特點與使用:
- Isolate有著自己的記憶體並且不存在通過共享記憶體的方式實現Isolate之間的通訊;
- 每個Isolate都有自己的事件迴圈;
- 在Isolate中是通過Port進行通訊的。Port分為兩種一種是receive port用於接收訊息。另外一種是send port用於傳送訊息;
- 使用Isolate時,可以通過spwan()方法啟動一個Isolate
接下來通過一個例子展示Isolate的通訊以及建立過程。
main() async {
var receivePort = new ReceivePort();
/**
* 初始化另外一個Isolate,
* 將main isolate的send port傳送到child isolate中,
* 用於向main isolate傳送資訊
*/
await Isolate.spawn(echo, receivePort.sendPort);
// child isolate的第一個資訊作為child isolate的send port,由於向child isolate傳送資訊
var sendPort = await receivePort.first;
var msg = await sendReceive(sendPort, "hello");
print('received $msg');
msg = await sendReceive(sendPort, "close");
print('received $msg');
}
// child isolate的入口
echo(SendPort sendPort) async {
// 開啟一個接收埠
var port = new ReceivePort();
//監聽接收的資訊
port.listen((dynamic msg) {
print("child receive $msg");
var data = msg[0];
SendPort replyTo = msg[1];
replyTo.send(data);
if (data == "close") port.close(); //關閉接收埠
});
// 向其他的isolate通知child isolate的傳送埠.
sendPort.send(port.sendPort);
}
//傳送資訊
Future sendReceive(SendPort port, msg) {
ReceivePort response = new ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
複製程式碼
總結
以上就是關於Dart的非同步以及事件迴圈原理。理解這些內容是必要的,因為在很多的開發場景都會用到非同步,同時需要很好的理解Dart的事件處理機制。