Dart中的非同步與事件迴圈

Codeing_ls發表於2019-09-09

前言

Dart作為Flutter的開發語言,瞭解Dart的機制是必可少的。本篇文章就介紹一下Dart的非同步操作與事件迴圈機制。

非同步操作我們都知道在開發過程中,如果有耗時操作,我們一般都會使用非同步任務解決,以防主執行緒卡頓。

事件迴圈是Dart中處理事件的一種機制。Flutter中就是通過事件迴圈來驅動程式的執行,這點與Android中的Handler有點類似。

Dart的事件迴圈機制

Dart語言是單執行緒模型的語言。這也就意味著Dart在同一時刻只能執行一個操作,其他操作在這個操作之後執行。那麼Dart的其他操作如何執行呢?在Dart中通過Event Loop來驅動事件的執行。

  • Dart程式啟動時會建立兩個佇列,分別是MicroTak佇列(微服務佇列)和Event佇列(事件佇列);
  • 然後執行main()方法;
  • 最後啟動事件迴圈;

Dart中的非同步與事件迴圈

上面的圖片描述了Dart的事件迴圈機制。可以看到,Dart執行事件的流程是:

  1. 執行main();
  2. 執行MicroTask佇列中事件;
  3. 執行事件佇列中的事件;

可以看到,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的事件處理機制。

相關文章