一 . async await 與 Future
在異步調(diào)用中有三個(gè)關(guān)鍵詞矫废,async岂膳、await、Future纳猫,其中async和await需要一起使用婆咸。在Dart中可以通過async和await進(jìn)行異步操作,async表示開啟一個(gè)異步操作续担,也可以返回一個(gè)Future結(jié)果擅耽。如果沒有返回值,則默認(rèn)返回一個(gè)返回值為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()方法,會同步進(jìn)入方法內(nèi)部進(jìn)行執(zhí)行憾儒,當(dāng)執(zhí)行到await時(shí)就會停止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)部就有一個(gè)await操作,當(dāng)執(zhí)行到await時(shí)就會停止_testAsyncKeyword() async內(nèi)部的執(zhí)行.等待_testString()有結(jié)果返回之后,繼續(xù)執(zhí)行.
_testString()內(nèi)部也是有一個(gè)await操作,當(dāng)執(zhí)行到await時(shí)就會停止_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 "我是一個(gè)字符串";
});
}
Future<String> secondString() {
return Future.delayed(Duration(milliseconds: 200), () {
return "我是二個(gè)字符串";
});
}
Future<String> thirdString() {
return Future.delayed(Duration(milliseconds: 100), () {
return "我是三個(gè)字符串";
});
}
//_testAsyncKeyword() 的打印:
//flutter: test函數(shù)開始了:2021-05-27 14:17:42.620409
//flutter: test函數(shù)結(jié)束了:2021-05-27 14:17:42.624359
//flutter: 我是三個(gè)字符串
//flutter: 我是二個(gè)字符串
//flutter: 我是一個(gè)字符串
//_testKeyword2() 的打印:
//flutter: test函數(shù)開始了:2021-05-27 14:18:41.401992
//flutter: 我是一個(gè)字符串
//flutter: 我是二個(gè)字符串
//flutter: 我是三個(gè)字符串
//flutter: test函數(shù)結(jié)束了:2021-05-27 14:18:42.027575
通過上面三個(gè)例子 , 大家應(yīng)該可以看出 await async 和 then之間的區(qū)別和聯(lián)系了吧.
二. async眶根、await的原理
async、await的操作屬于"假異步",這是為什么呢?
如果想要得到這個(gè)問題的答案,首先我們需要了解async边琉、await的原理属百,了解協(xié)程的概念,因?yàn)閍sync变姨、await本質(zhì)上就是協(xié)程的一種語法糖族扰。協(xié)程,也叫作coroutine,是一種比線程更小的單元渔呵。如果從單元大小來說怒竿,基本可以理解為 進(jìn)程->線程->協(xié)程。
1.任務(wù)調(diào)度
在弄懂協(xié)程之前扩氢,首先要明白并發(fā)和并行的概念
- 并發(fā): 指的是由系統(tǒng)來管理多個(gè)IO的切換耕驰,并交由CPU去處理。
- 并行: 指的是多核CPU在同一時(shí)間里執(zhí)行多個(gè)任務(wù)录豺。
并發(fā)的實(shí)現(xiàn)由非阻塞操作+事件通知來完成朦肘,事件通知也叫做“中斷”。操作過程分為兩種巩检,一種是CPU對IO進(jìn)行操作厚骗,在操作完成后發(fā)起中斷告訴IO操作完成。另一種是IO發(fā)起中斷兢哭,告訴CPU可以進(jìn)行操作领舰。
線程: 本質(zhì)上也是依賴于中斷來進(jìn)行調(diào)度的,線程還有一種叫做“阻塞式中斷”迟螺,就是在執(zhí)行IO操作時(shí)將線程阻塞冲秽,等待執(zhí)行完成后再繼續(xù)執(zhí)行,通過單線程并發(fā)可以進(jìn)行大量并發(fā)操作。但線程的消耗是很大的矩父,并不適合大量并發(fā)操作的處理,且單個(gè)線程只能使用到單個(gè)CPU锉桑。當(dāng)多核CPU出現(xiàn)后,單個(gè)線程就無法很好的利用多核CPU的優(yōu)勢了窍株,所以又引入了線程池的概念民轴,通過線程池來管理大量線程。當(dāng)需要同時(shí)執(zhí)行多項(xiàng)任務(wù)的時(shí)候球订,我們就會采用多線程并發(fā)執(zhí)行.
單線程并發(fā) http://www.reibang.com/p/65e7f173024e
Dart單線程運(yùn)行模型: 輸入單吸納成運(yùn)行機(jī)制,主要是通過消息循環(huán)機(jī)制來實(shí)現(xiàn)任務(wù)調(diào)度和處理的.
2.協(xié)程coroutine
關(guān)于協(xié)程 https://zhuanlan.zhihu.com/p/172471249
多線程并發(fā) 操作系統(tǒng)在線程等待IO的時(shí)候后裸,會阻塞當(dāng)前線程,切換到其它線程冒滩,這樣在當(dāng)前線程等待IO的過程中微驶,其它線程可以繼續(xù)執(zhí)行。當(dāng)系統(tǒng)線程較少的時(shí)候沒有什么問題开睡,但是當(dāng)線程數(shù)量非常多的時(shí)候因苹,卻產(chǎn)生了問題。一是系統(tǒng)線程會占用非常多的內(nèi)存空間篇恒,二是過多的線程切換會占用大量的系統(tǒng)時(shí)間扶檐。
協(xié)程 運(yùn)行在線程之上,當(dāng)一個(gè)協(xié)程執(zhí)行完成后婚度,可以選擇主動讓出蘸秘,讓另一個(gè)協(xié)程運(yùn)行在當(dāng)前線程之上官卡。協(xié)程并沒有增加線程數(shù)量,只是在線程的基礎(chǔ)之上通過分時(shí)復(fù)用的方式運(yùn)行多個(gè)協(xié)程醋虏,而且協(xié)程的切換在用戶態(tài)完成寻咒,切換的代價(jià)比線程從用戶態(tài)到內(nèi)核態(tài)的代價(jià)小很多。
協(xié)程分為無線協(xié)程和有線協(xié)程.
- 無線協(xié)程在離開當(dāng)前調(diào)用位置時(shí)颈嚼,會將當(dāng)前變量放在 堆區(qū)毛秘,當(dāng)再次回到當(dāng)前位置時(shí),還會繼續(xù)從堆區(qū)中獲取到變量阻课。所以叫挟,一般在執(zhí)行當(dāng)前函數(shù)時(shí)就會將變量直接分配到堆區(qū),而async限煞、await就屬于無線協(xié)程的一種抹恳。
- 有線協(xié)程則會將變量繼續(xù)保存在 棧區(qū),在回到指針指向的離開位置時(shí)署驻,會繼續(xù)從棧中取出調(diào)用奋献。
3.async、await原理
之所以說async/await是假異步,是因?yàn)樗趫?zhí)行過程中并沒有開啟新的線程更沒有并發(fā)執(zhí)行,而是通過單線程上的任務(wù)調(diào)度(協(xié)程,沒有并發(fā)執(zhí)行功能)實(shí)現(xiàn)的:
當(dāng)代碼執(zhí)行到async則表示進(jìn)入一個(gè)協(xié)程旺上,會同步執(zhí)行async的代碼塊瓶蚂。async的代碼塊本質(zhì)上也相當(dāng)于一個(gè)函數(shù),并且有自己的上下文環(huán)境宣吱。當(dāng)執(zhí)行到await時(shí)窃这,則表示有任務(wù)需要等待,CPU則去調(diào)度執(zhí)行其他IO征候,也就是后面的代碼或其他協(xié)程代碼杭攻。過一段時(shí)間CPU就會輪詢一次,看某個(gè)協(xié)程是否任務(wù)已經(jīng)處理完成疤坝,有返回結(jié)果可以被繼續(xù)執(zhí)行朴上,如果可以被繼續(xù)執(zhí)行的話,則會沿著上次離開時(shí)指針指向的位置繼續(xù)執(zhí)行卒煞,也就是await標(biāo)志的位置。
由于并沒有開啟新的線程叼架,只是進(jìn)行IO中斷改變CPU調(diào)度畔裕,所以網(wǎng)絡(luò)請求這樣的異步操作可以使用async、await乖订,但如果是執(zhí)行大量耗時(shí)同步操作的話扮饶,應(yīng)該使用isolate開辟新的線程去執(zhí)行。