一 . async await 與 Future
在異步調(diào)用中有三個關(guān)鍵詞,async、await、Future,其中async和await需要一起使用。在Dart中可以通過async和await進行異步操作,async表示開啟一個異步操作,也可以返回一個Future結(jié)果。如果沒有返回值,則默認返回一個返回值為null的Future。
await的操作,不會影響方法外后續(xù)代碼的執(zhí)行;只會阻塞async方法的后續(xù)代碼
例子1
_testAsyncKeyword() {
print("test函數(shù)開始了:${DateTime.now()}");
_testString().then((value) => print(value));
print("test函數(shù)結(jié)束了:${DateTime.now()}");
}
Future<String> _testString() async {
Future f = Future.delayed(Duration(milliseconds: 300), () {
return "我是測試字符串===1";
});
String result = await f;
print("我是測試字符串===2");
return result;
}
// flutter: test函數(shù)開始了:2021-05-27 13:58:56.595964
// flutter: test函數(shù)結(jié)束了:2021-05-27 13:58:56.598801
// flutter: 我是測試字符串===2
// flutter: 我是測試字符串===1
在代碼示例中,執(zhí)行到_testString()方法,會同步進入方法內(nèi)部進行執(zhí)行,當(dāng)執(zhí)行到await時就會停止async內(nèi)部的執(zhí)行,從而繼續(xù)執(zhí)行外面的代碼。所以await的操作,不會影響后面代碼的執(zhí)行("test函數(shù)結(jié)束了"會先于_testString()內(nèi)部打印)。
當(dāng)await有返回后,會繼續(xù)從await的位置繼續(xù)執(zhí)行(所以先打印出了 "我是測試字符串===2" ,然后返回Future的結(jié)果,并通過print打印出 "我是測試字符串===1")。
例子2
_testAsyncKeyword() async {
print("test函數(shù)開始了:${DateTime.now()}");
print(await _testString());
print("test函數(shù)結(jié)束了:${DateTime.now()}");
}
Future<String> _testString() async {
Future f = Future.delayed(Duration(milliseconds: 300), () {
return "我是測試字符串===1";
});
String result = await f;
print("我是測試字符串===2");
return result;
}
// flutter: test函數(shù)開始了:2021-05-27 14:06:48.185704
// flutter: 我是測試字符串===2
// flutter: 我是測試字符串===1
// flutter: test函數(shù)結(jié)束了:2021-05-27 14:06:48.497481
在代碼示例中, _testAsyncKeyword() 本身內(nèi)部就有一個await操作,當(dāng)執(zhí)行到await時就會停止_testAsyncKeyword() async內(nèi)部的執(zhí)行.等待_testString()有結(jié)果返回之后,繼續(xù)執(zhí)行.
_testString()內(nèi)部也是有一個await操作,當(dāng)執(zhí)行到await時就會停止_testString() async內(nèi)部的執(zhí)行,等待300毫秒,Future有結(jié)果后,打印字符串2
_testAsyncKeyword() 繼續(xù)執(zhí)行 打印 字符串1 及 結(jié)束
例子3
_testAsyncKeyword() {
print("test函數(shù)開始了:${DateTime.now()}");
firstString().then((value) => print(value));
secondString().then((value) => print(value));
thirdString().then((value) => print(value));
print("test函數(shù)結(jié)束了:${DateTime.now()}");
}
_testKeyword2() async{
print("test函數(shù)開始了:${DateTime.now()}");
print(await firstString());
print(await secondString());
print(await thirdString());
print("test函數(shù)結(jié)束了:${DateTime.now()}");
}
Future<String> firstString() {
return Future.delayed(Duration(milliseconds: 300), () {
return "我是一個字符串";
});
}
Future<String> secondString() {
return Future.delayed(Duration(milliseconds: 200), () {
return "我是二個字符串";
});
}
Future<String> thirdString() {
return Future.delayed(Duration(milliseconds: 100), () {
return "我是三個字符串";
});
}
//_testAsyncKeyword() 的打印:
//flutter: test函數(shù)開始了:2021-05-27 14:17:42.620409
//flutter: test函數(shù)結(jié)束了:2021-05-27 14:17:42.624359
//flutter: 我是三個字符串
//flutter: 我是二個字符串
//flutter: 我是一個字符串
//_testKeyword2() 的打印:
//flutter: test函數(shù)開始了:2021-05-27 14:18:41.401992
//flutter: 我是一個字符串
//flutter: 我是二個字符串
//flutter: 我是三個字符串
//flutter: test函數(shù)結(jié)束了:2021-05-27 14:18:42.027575
通過上面三個例子 , 大家應(yīng)該可以看出 await async 和 then之間的區(qū)別和聯(lián)系了吧.
二. async、await的原理
async、await的操作屬于"假異步",這是為什么呢?
如果想要得到這個問題的答案,首先我們需要了解async、await的原理,了解協(xié)程的概念,因為async、await本質(zhì)上就是協(xié)程的一種語法糖。協(xié)程,也叫作coroutine,是一種比線程更小的單元。如果從單元大小來說,基本可以理解為 進程->線程->協(xié)程。
1.任務(wù)調(diào)度
在弄懂協(xié)程之前,首先要明白并發(fā)和并行的概念
- 并發(fā): 指的是由系統(tǒng)來管理多個IO的切換,并交由CPU去處理。
- 并行: 指的是多核CPU在同一時間里執(zhí)行多個任務(wù)。
并發(fā)的實現(xiàn)由非阻塞操作+事件通知來完成,事件通知也叫做“中斷”。操作過程分為兩種,一種是CPU對IO進行操作,在操作完成后發(fā)起中斷告訴IO操作完成。另一種是IO發(fā)起中斷,告訴CPU可以進行操作。
線程: 本質(zhì)上也是依賴于中斷來進行調(diào)度的,線程還有一種叫做“阻塞式中斷”,就是在執(zhí)行IO操作時將線程阻塞,等待執(zhí)行完成后再繼續(xù)執(zhí)行,通過單線程并發(fā)可以進行大量并發(fā)操作。但線程的消耗是很大的,并不適合大量并發(fā)操作的處理,且單個線程只能使用到單個CPU。當(dāng)多核CPU出現(xiàn)后,單個線程就無法很好的利用多核CPU的優(yōu)勢了,所以又引入了線程池的概念,通過線程池來管理大量線程。當(dāng)需要同時執(zhí)行多項任務(wù)的時候,我們就會采用多線程并發(fā)執(zhí)行.
單線程并發(fā) http://www.itdecent.cn/p/65e7f173024e
Dart單線程運行模型: 輸入單吸納成運行機制,主要是通過消息循環(huán)機制來實現(xiàn)任務(wù)調(diào)度和處理的.
2.協(xié)程coroutine
關(guān)于協(xié)程 https://zhuanlan.zhihu.com/p/172471249
多線程并發(fā) 操作系統(tǒng)在線程等待IO的時候,會阻塞當(dāng)前線程,切換到其它線程,這樣在當(dāng)前線程等待IO的過程中,其它線程可以繼續(xù)執(zhí)行。當(dāng)系統(tǒng)線程較少的時候沒有什么問題,但是當(dāng)線程數(shù)量非常多的時候,卻產(chǎn)生了問題。一是系統(tǒng)線程會占用非常多的內(nèi)存空間,二是過多的線程切換會占用大量的系統(tǒng)時間。
協(xié)程 運行在線程之上,當(dāng)一個協(xié)程執(zhí)行完成后,可以選擇主動讓出,讓另一個協(xié)程運行在當(dāng)前線程之上。協(xié)程并沒有增加線程數(shù)量,只是在線程的基礎(chǔ)之上通過分時復(fù)用的方式運行多個協(xié)程,而且協(xié)程的切換在用戶態(tài)完成,切換的代價比線程從用戶態(tài)到內(nèi)核態(tài)的代價小很多。
協(xié)程分為無線協(xié)程和有線協(xié)程.
- 無線協(xié)程在離開當(dāng)前調(diào)用位置時,會將當(dāng)前變量放在 堆區(qū),當(dāng)再次回到當(dāng)前位置時,還會繼續(xù)從堆區(qū)中獲取到變量。所以,一般在執(zhí)行當(dāng)前函數(shù)時就會將變量直接分配到堆區(qū),而async、await就屬于無線協(xié)程的一種。
- 有線協(xié)程則會將變量繼續(xù)保存在 棧區(qū),在回到指針指向的離開位置時,會繼續(xù)從棧中取出調(diào)用。
3.async、await原理
之所以說async/await是假異步,是因為他在執(zhí)行過程中并沒有開啟新的線程更沒有并發(fā)執(zhí)行,而是通過單線程上的任務(wù)調(diào)度(協(xié)程,沒有并發(fā)執(zhí)行功能)實現(xiàn)的:
當(dāng)代碼執(zhí)行到async則表示進入一個協(xié)程,會同步執(zhí)行async的代碼塊。async的代碼塊本質(zhì)上也相當(dāng)于一個函數(shù),并且有自己的上下文環(huán)境。當(dāng)執(zhí)行到await時,則表示有任務(wù)需要等待,CPU則去調(diào)度執(zhí)行其他IO,也就是后面的代碼或其他協(xié)程代碼。過一段時間CPU就會輪詢一次,看某個協(xié)程是否任務(wù)已經(jīng)處理完成,有返回結(jié)果可以被繼續(xù)執(zhí)行,如果可以被繼續(xù)執(zhí)行的話,則會沿著上次離開時指針指向的位置繼續(xù)執(zhí)行,也就是await標(biāo)志的位置。
由于并沒有開啟新的線程,只是進行IO中斷改變CPU調(diào)度,所以網(wǎng)絡(luò)請求這樣的異步操作可以使用async、await,但如果是執(zhí)行大量耗時同步操作的話,應(yīng)該使用isolate開辟新的線程去執(zhí)行。