Dart 非同步程式設計相關概念簡述

crash_coder發表於2019-07-02

​ 學習 Dart 的非同步程式設計時,需要對非同步程式設計所涉及的相關知識體系進行梳理,我們可根據以下幾個發問來逐個瞭解非同步程式設計涉及的內容:

  • 為什麼需要非同步程式設計?
  • 非同步程式設計的內在機制是什麼?
  • Dart 中如何進行非同步程式設計?

isolate:

Dart是單“執行緒”語言:

Dart 程式碼在某個 isolate 的上下文中執行,該 isolate 擁有 Dart 程式碼所需的所有記憶體。當Dart 程式碼正在執行時,同一個 isolate 中的其他程式碼都無法執行,更通俗地講,Dart 一次執行一個操作,這意味著只要一個操作正在執行,它就不會被任何其他 Dart 程式碼中斷。這就引發出以下問題:

1,如果 Dart 程式碼正在執行長耗時計算,或者等待 I/O,將會導致程式凍結;

2,如果需要同時執行不同的 Dart 程式碼,就需要將這些程式碼放在不同的 isolate 環境中執行 ;

3, 如何在 Dart 中編寫非同步程式碼,進行非同步操作?(所謂非同步操作,指的是當你的程式在等待其它操作完成時,可以讓其先完成其它部分的操作)

我們先放碼出來:

main() {
  fallInLove();//談戀愛
  House newHourse = buyHouse();//買房(耗時、費力操作)
  livingHouse(newHourse);//買完房子之後住進新房
  marry();//結婚
  haveChild();//生娃
}

如上所示,在這種情況下,只有當第一條語句執行完之後,後面的語句才能接著按順序執行;

大部分人的人生三件事:買房,結婚,生娃

這三件大事,如果用 Dart 語言來實現:

如果是在同步操作中,並且家裡沒礦,六個錢包也湊不齊首付的話,你的人生就會凍結- -等著買完房之後,才能結婚,結完婚之後才能生娃;

有沒有什麼操作可以實現先結婚,再生娃,再買房呢?答案是有的:如果將買房(buyHouse())放在非同步操作裡,這時候你要給你的丈母孃及老婆一個承諾:以後有錢了再買房!現在先結婚、生娃!詳情我們後文再敘,這裡先提出這麼個設想;

event loop:

當開始執行 Flutter 或者 Dart 應用程式時,一個 isolate 就會被建立並啟動,當 isolate 建立成功時,Dart 就會進行如下三個事項:

1,初始化兩個先進先出(FIFO)佇列:一個稱為 MicroTask 佇列,另外一個稱為 Event 佇列;

2,執行main()方法,且執行完成後,進入3

3,啟動 Event Loop

4,開始處理兩個佇列中的元素(兩個佇列的執行有先後順序,見後文)

也就是說,每個 isolate 中都會有且只有一個Event Loop(事件迴圈)和兩個佇列(MicroTask Queue、Event Queue ); Event Loop 將根據MicroTask佇列和Event佇列裡的內容來驅動程式碼的執行方式和順序。

Add the main isolate executing tasks off the queue: main(), then key event handler, then click event handler, then timer task, etc.

那麼問題來了:

1,Event Loop是什麼?用來幹啥的?

2,MicroTask佇列和Event佇列都分別是什麼?有什麼用?

3,兩者有什麼區別?

Event Loop是一個定期喚醒的無限迴圈:它在MicroTask、Event佇列中查詢是否有需要執行的任務。如果佇列中的任務存在,則當且僅當CPU空閒時,Event Loop將它們放入執行堆疊執行。

MicroTask Queue用於非常短的,需要非同步執行的操作,考慮如下場景:想要在稍後完成一些任務但又希望是在執行下一個Event佇列之前;一般使用dart:async庫中的scheduleMicrotask方法來實現;

Event Queue(事件佇列)包含所有外部事件:

  • I/O,
  • 手勢,
  • 滑鼠事件,
  • 繪圖事件,
  • 計時器,
  • isolate 之間的訊息
  • Future

每次外部事件被觸發時,要執行的相應程式碼都會被新增到 Event Queue 中,當MicroTask佇列中沒有任何內容時,Event Loop才會從Event 佇列中取出第一項來處理;需要重點關注的是,Future也會被新增到 Event 佇列中

當main()方法執行完成後,event loop開始它的工作,

1,先從 microtask 佇列以先進先出的方式取出並執行完所有內容;

2,從event 佇列中取出並處理第一項;

3,重複上述兩個步驟直到兩個佇列都沒有任何內容可執行

綜上所述,可以由如下簡化圖來表示:

flowchart: main() -> microtasks -> next event -> microtasks -> ...

Future:

Future 通常指的是非同步執行的任務,它會在未來某個時間點完成,這裡的完成有兩層含義:成功或者失敗,成功時返回任務執行的結果(注意:這裡的結果指的是 Future< T> 返回範型T的物件),失敗時返回錯誤;

當例項化一個 Future 的時候:

  • 該 Future 的一個例項被建立並記錄在由 Dart 管理的內部陣列中;
  • 需要由此 Future 執行的程式碼直接被推送到 Event 佇列中;
  • Future 例項返回一個未完成狀態的 Future 物件;
  • 如果 Future 語句之後還有其它的話(不是 Future 包含著的程式碼),則繼續執行下一個同步程式碼;
  • Future 完成後,then()方法及catchError()方法裡的程式碼將會被執行;
import 'dart:async';

void main() {
  fallInLove(); //談戀愛;
  handleHouse(); //買房、入住(耗時、費用的操作)
  marry(); //結婚
  haveChild(); //生娃
}

///進行買房 [buyHouse]、入住[livingHouse]等操作
void handleHouse() {
  Future<House> house = buyHouse();
  house.then((_) {
    livingHouse();
  });
}

class House {}

Future<House> buyHouse() {
  Future<House> result = Future(() {
    print('buyHouse');
  });
  return result;
}

void livingHouse() {
  print('livingHouse');
}

void marry() {
  print('marry');
}

void haveChild() {
  print('haveChild');
}

void fallInLove() {
  print('fall in love');
}

我們來分析上述程式碼的執行順序:

  1. main()方法中開始執行同步程式碼,首先執行fallInLove();
  2. 執行handleHouse()方法,將Future裡的(){print('buyHouse');}加入 Event 佇列;
  3. 執行marry()方法;
  4. 執行haveChild()方法;
  5. 執行到這裡的時候,main()方法已經執行完了,Event Loop開始處理兩個佇列中的元素,如前面的分析,這時候先檢視MicroTask佇列有沒有需要處理的任務,沒有的話,就可以取出Event佇列中的第一個任務來執行,在這個例子中,就是開始執行步驟2的(){print('buyHouse');}程式碼塊;
  6. 當上述步驟完成之後,開始執行then()中的方法 livingHouse();;

所以程式碼的執行結果應該如下所示:

fall in love
marry
haveChild
buyHouse
livingHouse

async/await:

​ 上面的 Future 章節,我們主要使用了 Future 的 API 來達到非同步操作的目的,Dart 還為我們提供了一對關鍵字 async/await 來達成此目的;有了這兩個關鍵字,我們可以像寫同步程式碼那樣寫非同步程式碼,並且不用使用到 Future的 API (then());

使用 async/await 關鍵字有以下幾個需要注意的點:

  • async 關鍵字宣告的方法,需要返回 Future 物件:有可用值時型別為 Future< T>;無可用值時為 Future< void>;
  • await 關鍵字只能在 標記為 async 的方法裡出現;
  • await 關鍵字後面跟著的表示式,通常是 Future 物件,如果不是,系統會自動封裝成Future 物件;
  • await 表示式會使表示式之後的語句暫停執行(async方法裡的執行),直到返回的 Future物件可用;
  • 對應於使用 Future API 中的 catchError()方法,可以將await表示式包在 try/catch進行錯誤捕獲及後續處理;

如下:我們只需要稍微改造handleHouse()方法:

  1. 在方法的 body 前加 async關鍵字標誌
  2. handleHouse()方法的返回型別改為 Future< void>
  3. 在需要耗時的操作方法前加 await關鍵字標誌
///進行買房 [buyHouse]、入住[livingHouse]等操作
Future<void> handleHouse() async {
  await buyHouse();
  livingHouse();
}

執行程式碼後的輸出效果是與使用Future API 一致的;

總結

本文主要涉及到的概念有:isolate,event loop,future,async/await,理解了這些內容,可以讓我們更好地寫出、閱讀非同步程式設計的相關程式碼;

參考連結

參考連結1:https://dart.dev/tutorials/language/futures

參考連結2:https://dart.dev/guides/language/language-tour#asynchrony-support

參考連結3:https://dart.dev/articles/archive/event-loop#event-queue-new-future

相關文章