學習 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佇列裡的內容來驅動程式碼的執行方式和順序。
那麼問題來了:
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,重複上述兩個步驟直到兩個佇列都沒有任何內容可執行
綜上所述,可以由如下簡化圖來表示:
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');
}
我們來分析上述程式碼的執行順序:
- 從
main()
方法中開始執行同步程式碼,首先執行fallInLove()
; - 執行
handleHouse()
方法,將Future裡的(){print('buyHouse');}
加入 Event 佇列; - 執行
marry()
方法; - 執行
haveChild()
方法; - 執行到這裡的時候,
main()
方法已經執行完了,Event Loop開始處理兩個佇列中的元素,如前面的分析,這時候先檢視MicroTask佇列有沒有需要處理的任務,沒有的話,就可以取出Event佇列中的第一個任務來執行,在這個例子中,就是開始執行步驟2的(){print('buyHouse');}
程式碼塊; - 當上述步驟完成之後,開始執行
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()
方法:
- 在方法的 body 前加 async關鍵字標誌
- 將
handleHouse()
方法的返回型別改為 Future< void> - 在需要耗時的操作方法前加 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