一. Dart的異步模型
我們先來(lái)搞清楚Dart是如何搞定異步操作的
1.1. Dart是單線(xiàn)程的
1.1.1. 程序中的耗時(shí)操作
開(kāi)發(fā)中的耗時(shí)操作:
在開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到一些耗時(shí)的操作需要完成占遥,比如網(wǎng)絡(luò)請(qǐng)求俯抖、文件讀取等等;
如果我們的主線(xiàn)程一直在等待這些耗時(shí)的操作完成筷频,那么就會(huì)進(jìn)行阻塞蚌成,無(wú)法響應(yīng)其它事件前痘,比如用戶(hù)的點(diǎn)擊;
顯然担忧,我們不能這么干G鄣蕖!
如何處理耗時(shí)的操作呢瓶盛?
針對(duì)如何處理耗時(shí)的操作最欠,不同的語(yǔ)言有不同的處理方式。
處理方式一: 多線(xiàn)程惩猫,比如Java芝硬、C++,我們普遍的做法是開(kāi)啟一個(gè)新的線(xiàn)程(Thread)轧房,在新的線(xiàn)程中完成這些異步的操作拌阴,再通過(guò)線(xiàn)程間通信的方式,將拿到的數(shù)據(jù)傳遞給主線(xiàn)程奶镶。
處理方式二: 單線(xiàn)程+事件循環(huán)迟赃,比如JavaScript、Dart都是基于單線(xiàn)程加事件循環(huán)來(lái)完成耗時(shí)操作的處理厂镇。不過(guò)單線(xiàn)程如何能進(jìn)行耗時(shí)的操作呢纤壁?!
1.1.2. 單線(xiàn)程的異步操作
我之前碰到很多開(kāi)發(fā)者都對(duì)單線(xiàn)程的異步操作充滿(mǎn)了問(wèn)號(hào)捺信?酌媒??
其實(shí)它們并不沖突:
因?yàn)槲覀兊囊粋€(gè)應(yīng)用程序大部分時(shí)間都是處于空閑的狀態(tài)的迄靠,并不是無(wú)限制的在和用戶(hù)進(jìn)行交互秒咨。
比如等待用戶(hù)點(diǎn)擊吸申、網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)的返回礁叔、文件讀寫(xiě)的IO操作,這些等待的行為并不會(huì)阻塞我們的線(xiàn)程;
這是因?yàn)轭?lèi)似于網(wǎng)絡(luò)請(qǐng)求疫诽、文件讀寫(xiě)的IO,我們都可以基于非阻塞調(diào)用旦委;
阻塞式調(diào)用和非阻塞式調(diào)用
如果想搞懂這個(gè)點(diǎn)奇徒,我們需要知道操作系統(tǒng)中的阻塞式調(diào)用
和非阻塞式調(diào)用
的概念。
阻塞和非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果(消息缨硝,返回值)時(shí)的狀態(tài)摩钙。
阻塞式調(diào)用: 調(diào)用結(jié)果返回之前,當(dāng)前線(xiàn)程會(huì)被掛起查辩,調(diào)用線(xiàn)程只有在得到調(diào)用結(jié)果之后才會(huì)繼續(xù)執(zhí)行胖笛。
非阻塞式調(diào)用: 調(diào)用執(zhí)行之后网持,當(dāng)前線(xiàn)程不會(huì)停止執(zhí)行,只需要過(guò)一段時(shí)間來(lái)檢查一下有沒(méi)有結(jié)果返回即可长踊。
我們用一個(gè)生活中的例子來(lái)模擬:
你中午餓了功舀,需要點(diǎn)一份外賣(mài),點(diǎn)
外賣(mài)的動(dòng)作
就是我們的調(diào)用身弊,拿到最后點(diǎn)的外賣(mài)
就是我們要等待的結(jié)果辟汰。阻塞式調(diào)用: 點(diǎn)了外賣(mài),不再做任何事情阱佛,就是在傻傻的等待帖汞,你的線(xiàn)程停止了任何其他的工作。
非阻塞式調(diào)用: 點(diǎn)了外賣(mài)凑术,繼續(xù)做其他事情:繼續(xù)工作翩蘸、打把游戲,你的線(xiàn)程沒(méi)有繼續(xù)執(zhí)行其他事情淮逊,只需要偶爾去看一下有沒(méi)有人敲門(mén)鹿鳖,外賣(mài)有沒(méi)有送到即可。
而我們開(kāi)發(fā)中的很多耗時(shí)操作壮莹,都可以基于這樣的 非阻塞式調(diào)用
:
比如網(wǎng)絡(luò)請(qǐng)求本身使用了Socket通信翅帜,而Socket本身提供了select模型,可以進(jìn)行
非阻塞方式的工作
命满;比如文件讀寫(xiě)的IO操作涝滴,我們可以使用操作系統(tǒng)提供的
基于事件的回調(diào)機(jī)制
;
這些操作都不會(huì)阻塞我們單線(xiàn)程的繼續(xù)執(zhí)行胶台,我們的線(xiàn)程在等待的過(guò)程中可以繼續(xù)去做別的事情:喝杯咖啡歼疮、打把游戲,等真正有了響應(yīng)诈唬,再去進(jìn)行對(duì)應(yīng)的處理即可韩脏。
這時(shí),我們可能有兩個(gè)問(wèn)題:
問(wèn)題一: 如果在多核CPU中铸磅,單線(xiàn)程是不是就沒(méi)有充分利用CPU呢赡矢?這個(gè)問(wèn)題,我會(huì)放在后面來(lái)講解阅仔。
問(wèn)題二: 單線(xiàn)程是如何來(lái)處理網(wǎng)絡(luò)通信吹散、IO操作它們返回的結(jié)果呢?答案就是事件循環(huán)(Event Loop)八酒。
1.2. Dart事件循環(huán)
1.2.1. 什么是事件循環(huán)
單線(xiàn)程模型中主要就是在維護(hù)著一個(gè)事件循環(huán)(Event Loop)空民。
事件循環(huán)是什么呢?
事實(shí)上事件循環(huán)并不復(fù)雜羞迷,它就是將需要處理的一系列事件(包括點(diǎn)擊事件界轩、IO事件画饥、網(wǎng)絡(luò)事件)放在一個(gè)事件隊(duì)列(Event Queue)中。
不斷的從事件隊(duì)列(Event Queue)中取出事件浊猾,并執(zhí)行其對(duì)應(yīng)需要執(zhí)行的代碼塊荒澡,直到事件隊(duì)列清空為止。
我們來(lái)寫(xiě)一個(gè)事件循環(huán)的偽代碼:
// 這里我使用數(shù)組模擬隊(duì)列, 先進(jìn)先出的原則
List eventQueue = [];
var event;
// 事件循環(huán)從啟動(dòng)的一刻与殃,永遠(yuǎn)在執(zhí)行
while (true) {
if (eventQueue.length > 0) {
// 取出一個(gè)事件
event = eventQueue.removeAt(0);
// 執(zhí)行該事件
event();
}
}
當(dāng)我們有一些事件時(shí)单山,比如點(diǎn)擊事件、IO事件幅疼、網(wǎng)絡(luò)事件時(shí)米奸,它們就會(huì)被加入到eventLoop
中,當(dāng)發(fā)現(xiàn)事件隊(duì)列不為空時(shí)發(fā)現(xiàn)爽篷,就會(huì)取出事件悴晰,并且執(zhí)行。
- 齒輪就是我們的事件循環(huán)逐工,它會(huì)從隊(duì)列中一次取出事件來(lái)執(zhí)行铡溪。
1.2.2. 事件循環(huán)代碼模擬
這里我們來(lái)看一段偽代碼,理解點(diǎn)擊事件和網(wǎng)絡(luò)請(qǐng)求的事件是如何被執(zhí)行的:
這是一段Flutter代碼泪喊,很多東西大家可能不是特別理解棕硫,但是耐心閱讀你會(huì)讀懂我們?cè)谧鍪裁础?/p>
一個(gè)按鈕RaisedButton,當(dāng)發(fā)生點(diǎn)擊時(shí)執(zhí)行onPressed函數(shù)袒啼。
onPressed函數(shù)中哈扮,我們發(fā)送了一個(gè)網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求成功后會(huì)執(zhí)行then中的回調(diào)函數(shù)蚓再。
RaisedButton(
child: Text('Click me'),
onPressed: () {
final myFuture = http.get('https://example.com');
myFuture.then((response) {
if (response.statusCode == 200) {
print('Success!');
}
});
},
)
這些代碼是如何放在事件循環(huán)中執(zhí)行呢滑肉?
1、當(dāng)用戶(hù)發(fā)生點(diǎn)擊的時(shí)候摘仅,onPressed回調(diào)函數(shù)被放入事件循環(huán)中執(zhí)行靶庙,執(zhí)行的過(guò)程中發(fā)送了一個(gè)網(wǎng)絡(luò)請(qǐng)求。
2娃属、網(wǎng)絡(luò)請(qǐng)求發(fā)出去后六荒,該事件循環(huán)不會(huì)被阻塞,而是發(fā)現(xiàn)要執(zhí)行的onPressed函數(shù)已經(jīng)結(jié)束膳犹,會(huì)將它丟棄掉恬吕。
3签则、網(wǎng)絡(luò)請(qǐng)求成功后须床,會(huì)執(zhí)行then中傳入的回調(diào)函數(shù),這也是一個(gè)事件渐裂,該事件被放入到事件循環(huán)中執(zhí)行豺旬,執(zhí)行完畢后钠惩,事件循環(huán)將其丟棄。
盡管onPressed和then中的回調(diào)有一些差異族阅,但是它們對(duì)于事件循環(huán)來(lái)說(shuō)篓跛,都是告訴它:我有一段代碼需要執(zhí)行,快點(diǎn)幫我完成坦刀。
二. Dart的異步操作
Dart中的異步操作主要使用Future以及async愧沟、await。
如果你之前有過(guò)前端的ES6鲤遥、ES7編程經(jīng)驗(yàn)沐寺,那么完全可以將Future理解成Promise,async盖奈、await和ES7中基本一致混坞。
但是如果沒(méi)有前端開(kāi)發(fā)經(jīng)驗(yàn),F(xiàn)uture以及async钢坦、await如何理解呢究孕?
2.1. 認(rèn)識(shí)Future
2.1.1. 同步的網(wǎng)絡(luò)請(qǐng)求
我們先來(lái)看一個(gè)例子吧:
在這個(gè)例子中,我使用getNetworkData來(lái)模擬了一個(gè)網(wǎng)絡(luò)請(qǐng)求爹凹;
該網(wǎng)絡(luò)請(qǐng)求需要3秒鐘的時(shí)間厨诸,之后返回?cái)?shù)據(jù);
import "dart:io";
main(List<String> args) {
print("main function start");
print(getNetworkData());
print("main function end");
}
String getNetworkData() {
sleep(Duration(seconds: 3));
return "network data";
}
這段代碼會(huì)運(yùn)行怎么的結(jié)果呢禾酱?
- getNetworkData會(huì)阻塞main函數(shù)的執(zhí)行
main function start
// 等待3秒
network data
main function end
顯然泳猬,上面的代碼不是我們想要的執(zhí)行效果,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求阻塞了main函數(shù)宇植,那么意味著其后所有的代碼都無(wú)法正常的繼續(xù)執(zhí)行得封。
2.1.2. 異步的網(wǎng)絡(luò)請(qǐng)求
我們來(lái)對(duì)我們上面的代碼進(jìn)行改進(jìn),代碼如下:
和剛才的代碼唯一的區(qū)別在于我使用了Future對(duì)象來(lái)將耗時(shí)的操作放在了其中傳入的函數(shù)中指郁;
稍后忙上,我們會(huì)講解它具體的一些API,我們就暫時(shí)知道我創(chuàng)建了一個(gè)Future實(shí)例即可闲坎;
import "dart:io";
main(List<String> args) {
print("main function start");
print(getNetworkData());
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
return "network data";
});
}
我們來(lái)看一下代碼的運(yùn)行結(jié)果:
1疫粥、這一次的代碼順序執(zhí)行,沒(méi)有出現(xiàn)任何的阻塞現(xiàn)象腰懂;
2梗逮、和之前直接打印結(jié)果不同,這次我們打印了一個(gè)Future實(shí)例绣溜;
結(jié)論:我們將一個(gè)耗時(shí)的操作隔離了起來(lái)慷彤,這個(gè)操作不會(huì)再影響我們的主線(xiàn)程執(zhí)行了。
問(wèn)題:我們?nèi)绾稳ツ玫阶罱K的結(jié)果呢?
main function start
Instance of 'Future<String>'
main function end
獲取Future得到的結(jié)果
有了Future之后底哗,如何去獲取請(qǐng)求到的結(jié)果:通過(guò).then的回調(diào):
main(List<String> args) {
print("main function start");
// 使用變量接收getNetworkData返回的future
var future = getNetworkData();
// 當(dāng)future實(shí)例有返回結(jié)果時(shí)岁诉,會(huì)自動(dòng)回調(diào)then中傳入的函數(shù)
// 該函數(shù)會(huì)被放入到事件循環(huán)中,被執(zhí)行
future.then((value) {
print(value);
});
print(future);
print("main function end");
}
上面代碼的執(zhí)行結(jié)果:
main function start
Instance of 'Future<String>'
main function end
// 3s后執(zhí)行下面的代碼
network data
執(zhí)行中出現(xiàn)異常
如果調(diào)用過(guò)程中出現(xiàn)了異常跋选,拿不到結(jié)果涕癣,如何獲取到異常的信息呢?
import "dart:io";
main(List<String> args) {
print("main function start");
var future = getNetworkData();
future.then((value) {
print(value);
}).catchError((error) { // 捕獲出現(xiàn)異常時(shí)的情況
print(error);
});
print(future);
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
// 不再返回結(jié)果前标,而是出現(xiàn)異常
// return "network data";
throw Exception("網(wǎng)絡(luò)請(qǐng)求出現(xiàn)錯(cuò)誤");
});
}
上面代碼的執(zhí)行結(jié)果:
main function start
Instance of 'Future<String>'
main function end
// 3s后沒(méi)有拿到結(jié)果坠韩,但是我們捕獲到了異常
Exception: 網(wǎng)絡(luò)請(qǐng)求出現(xiàn)錯(cuò)誤
2.1.3. Future使用補(bǔ)充
補(bǔ)充一:上面案例的小結(jié)
我們通過(guò)一個(gè)案例來(lái)學(xué)習(xí)了一些Future的使用過(guò)程:
1、創(chuàng)建一個(gè)Future(可能是我們創(chuàng)建的炼列,也可能是調(diào)用內(nèi)部API或者第三方API獲取到的一個(gè)Future同眯,總之你需要獲取到一個(gè)Future實(shí)例,F(xiàn)uture通常會(huì)對(duì)一些異步的操作進(jìn)行封裝)唯鸭;
2须蜗、通過(guò).then(成功回調(diào)函數(shù))的方式來(lái)監(jiān)聽(tīng)Future內(nèi)部執(zhí)行完成時(shí)獲取到的結(jié)果;
3目溉、通過(guò).catchError(失敗或異趁靼梗回調(diào)函數(shù))的方式來(lái)監(jiān)聽(tīng)Future內(nèi)部執(zhí)行失敗或者出現(xiàn)異常時(shí)的錯(cuò)誤信息;
補(bǔ)充二:Future的兩種狀態(tài)
事實(shí)上Future在執(zhí)行的整個(gè)過(guò)程中缭付,我們通常把它劃分成了兩種狀態(tài):
狀態(tài)一:未完成狀態(tài)(uncompleted)
- 執(zhí)行Future內(nèi)部的操作時(shí)(在上面的案例中就是具體的網(wǎng)絡(luò)請(qǐng)求過(guò)程柿估,我們使用了延遲來(lái)模擬),我們稱(chēng)這個(gè)過(guò)程為未完成狀態(tài)
狀態(tài)二:完成狀態(tài)(completed)
當(dāng)Future內(nèi)部的操作執(zhí)行完成陷猫,通常會(huì)返回一個(gè)值秫舌,或者拋出一個(gè)異常。
這兩種情況绣檬,我們都稱(chēng)Future為完成狀態(tài)足陨。
Dart官網(wǎng)有對(duì)這兩種狀態(tài)解析,之所以貼出來(lái)是區(qū)別于Promise的三種狀態(tài)
補(bǔ)充三:Future的鏈?zhǔn)秸{(diào)用
上面代碼我們可以進(jìn)行如下的改進(jìn):
- 我們可以在then中繼續(xù)返回值娇未,會(huì)在下一個(gè)鏈?zhǔn)降膖hen調(diào)用回調(diào)函數(shù)中拿到返回的結(jié)果
import "dart:io";
main(List<String> args) {
print("main function start");
getNetworkData().then((value1) {
print(value1);
return "content data2";
}).then((value2) {
print(value2);
return "message data3";
}).then((value3) {
print(value3);
});
print("main function end");
}
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
// 不再返回結(jié)果墨缘,而是出現(xiàn)異常
return "network data1";
});
}
打印結(jié)果如下:
main function start
main function end
// 3s后拿到結(jié)果
network data1
content data2
message data3
補(bǔ)充四:Future其他API
Future.value(value)
- 直接獲取一個(gè)完成的Future,該Future會(huì)直接調(diào)用then的回調(diào)函數(shù)
main(List<String> args) {
print("main function start");
Future.value("哈哈哈").then((value) {
print(value);
});
print("main function end");
}
打印結(jié)果如下:
main function start
main function end
哈哈哈
疑惑:為什么立即執(zhí)行零抬,但是哈哈哈
是在最后打印的呢镊讼?
- 這是因?yàn)镕uture中的then會(huì)作為新的任務(wù)會(huì)加入到事件隊(duì)列中(Event Queue),加入之后你肯定需要排隊(duì)執(zhí)行了
Future.error(object)
- 直接獲取一個(gè)完成的Future平夜,但是是一個(gè)發(fā)生異常的Future蝶棋,該Future會(huì)直接調(diào)用catchError的回調(diào)函數(shù)
main(List<String> args) {
print("main function start");
Future.error(Exception("錯(cuò)誤信息")).catchError((error) {
print(error);
});
print("main function end");
}
打印結(jié)果如下:
main function start
main function end
Exception: 錯(cuò)誤信息
Future.delayed(時(shí)間, 回調(diào)函數(shù))
在延遲一定時(shí)間時(shí)執(zhí)行回調(diào)函數(shù),執(zhí)行完回調(diào)函數(shù)后會(huì)執(zhí)行then的回調(diào)忽妒;
之前的案例玩裙,我們也可以使用它來(lái)模擬兼贸,但是直接學(xué)習(xí)這個(gè)API會(huì)讓大家更加疑惑;
main(List<String> args) {
print("main function start");
Future.delayed(Duration(seconds: 3), () {
return "3秒后的信息";
}).then((value) {
print(value);
});
print("main function end");
}
2.2. await献酗、async
2.2.1. 理論概念理解
如果你已經(jīng)完全搞懂了Future寝受,那么學(xué)習(xí)await坷牛、async應(yīng)該沒(méi)有什么難度罕偎。
await、async是什么呢京闰?
它們是Dart中的關(guān)鍵字
它們可以讓我們用
同步的代碼格式
颜及,去實(shí)現(xiàn)異步的調(diào)用過(guò)程
。并且蹂楣,通常一個(gè)async的函數(shù)會(huì)返回一個(gè)Future俏站。
我們已經(jīng)知道,F(xiàn)uture可以做到不阻塞我們的線(xiàn)程痊土,讓線(xiàn)程繼續(xù)執(zhí)行肄扎,并且在完成某個(gè)操作時(shí)改變自己的狀態(tài),并且回調(diào)then或者errorCatch回調(diào)赁酝。
如何生成一個(gè)Future呢犯祠?
1、通過(guò)我們前面學(xué)習(xí)的Future構(gòu)造函數(shù)酌呆,或者后面學(xué)習(xí)的Future其他API都可以衡载。
2、還有一種就是通過(guò)async的函數(shù)隙袁。
2.2.2. 案例代碼演練
我們來(lái)對(duì)之前的Future異步處理代碼進(jìn)行改造痰娱,改成await、async的形式菩收。
我們知道梨睁,如果直接這樣寫(xiě)代碼,代碼是不能正常執(zhí)行的:
因?yàn)镕uture.delayed返回的是一個(gè)Future對(duì)象娜饵,我們不能把它看成同步的返回?cái)?shù)據(jù):
"network data"
去使用也就是我們不能把這個(gè)異步的代碼當(dāng)做同步一樣去使用而姐!
import "dart:io";
main(List<String> args) {
print("main function start");
print(getNetworkData());
print("main function end");
}
String getNetworkData() {
var result = Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請(qǐng)求到的數(shù)據(jù):" + result;
}
現(xiàn)在我使用await修改下面這句代碼:
你會(huì)發(fā)現(xiàn),我在
Future.delayed
函數(shù)前加了一個(gè)await划咐。一旦有了這個(gè)關(guān)鍵字拴念,那么這個(gè)操作就會(huì)等待
Future.delayed
的執(zhí)行完畢,并且等待它的結(jié)果褐缠。
String getNetworkData() {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請(qǐng)求到的數(shù)據(jù):" + result;
}
修改后執(zhí)行代碼政鼠,會(huì)看到如下的錯(cuò)誤:
錯(cuò)誤非常明顯:await關(guān)鍵字必須存在于async函數(shù)中。
所以我們需要將
getNetworkData
函數(shù)定義成async函數(shù)队魏。
繼續(xù)修改代碼如下:
- 也非常簡(jiǎn)單公般,只需要在函數(shù)的()后面加上一個(gè)async關(guān)鍵字就可以了
String getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請(qǐng)求到的數(shù)據(jù):" + result;
}
運(yùn)行代碼万搔,依然報(bào)錯(cuò)(心想:你妹啊):
錯(cuò)誤非常明顯:使用async標(biāo)記的函數(shù)官帘,必須返回一個(gè)Future對(duì)象瞬雹。
所以我們需要繼續(xù)修改代碼,將返回值寫(xiě)成一個(gè)Future刽虹。
繼續(xù)修改代碼如下:
Future<String> getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請(qǐng)求到的數(shù)據(jù):" + result;
}
這段代碼應(yīng)該是我們理想當(dāng)中執(zhí)行的代碼了
我們現(xiàn)在可以像同步代碼一樣去使用Future異步返回的結(jié)果酗捌;
等待拿到結(jié)果之后和其他數(shù)據(jù)進(jìn)行拼接,然后一起返回涌哲;
返回的時(shí)候并不需要包裝一個(gè)Future胖缤,直接返回即可,但是返回值會(huì)默認(rèn)被包裝在一個(gè)Future中阀圾;
2.3. 讀取json案例
我這里給出了一個(gè)在Flutter項(xiàng)目中哪廓,讀取一個(gè)本地的json文件,并且轉(zhuǎn)換成模型對(duì)象初烘,返回出去的案例涡真;
這個(gè)案例作為大家學(xué)習(xí)前面Future和await、async的一個(gè)參考肾筐,我并不打算展開(kāi)來(lái)講哆料,因?yàn)樾枰玫紽lutter的相關(guān)知識(shí);
后面我會(huì)在后面的案例中再次講解它在Flutter中我使用的過(guò)程中局齿;
讀取json案例代碼(了解一下即可)
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'dart:async';
main(List<String> args) {
getAnchors().then((anchors) {
print(anchors);
});
}
class Anchor {
String nickname;
String roomName;
String imageUrl;
Anchor({
this.nickname,
this.roomName,
this.imageUrl
});
Anchor.withMap(Map<String, dynamic> parsedMap) {
this.nickname = parsedMap["nickname"];
this.roomName = parsedMap["roomName"];
this.imageUrl = parsedMap["roomSrc"];
}
}
Future<List<Anchor>> getAnchors() async {
// 1.讀取json文件
String jsonString = await rootBundle.loadString("assets/yz.json");
// 2.轉(zhuǎn)成List或Map類(lèi)型
final jsonResult = json.decode(jsonString);
// 3.遍歷List剧劝,并且轉(zhuǎn)成Anchor對(duì)象放到另一個(gè)List中
List<Anchor> anchors = new List();
for (Map<String, dynamic> map in jsonResult) {
anchors.add(Anchor.withMap(map));
}
return anchors;
}
三. Dart的異步補(bǔ)充
3.1. 任務(wù)執(zhí)行順序
3.1.1. 認(rèn)識(shí)微任務(wù)隊(duì)列
在前面學(xué)習(xí)學(xué)習(xí)中,我們知道Dart中有一個(gè)事件循環(huán)(Event Loop)來(lái)執(zhí)行我們的代碼抓歼,里面存在一個(gè)事件隊(duì)列(Event Queue)讥此,事件循環(huán)不斷從事件隊(duì)列中取出事件執(zhí)行。
但是如果我們嚴(yán)格來(lái)劃分的話(huà)谣妻,在Dart中還存在另一個(gè)隊(duì)列:微任務(wù)隊(duì)列(Microtask Queue)萄喳。
微任務(wù)隊(duì)列的優(yōu)先級(jí)要高于事件隊(duì)列;
也就是說(shuō)
事件循環(huán)
都是優(yōu)先執(zhí)行微任務(wù)隊(duì)列
中的任務(wù)蹋半,再執(zhí)行事件隊(duì)列
中的任務(wù)他巨;
那么在Flutter開(kāi)發(fā)中,哪些是放在事件隊(duì)列减江,哪些是放在微任務(wù)隊(duì)列呢染突?
所有的外部事件任務(wù)都在事件隊(duì)列中,如IO辈灼、計(jì)時(shí)器份企、點(diǎn)擊、以及繪制事件等巡莹;
而微任務(wù)通常來(lái)源于Dart內(nèi)部司志,并且微任務(wù)非常少甜紫。這是因?yàn)槿绻⑷蝿?wù)非常多,就會(huì)造成事件隊(duì)列排不上隊(duì)骂远,會(huì)阻塞任務(wù)隊(duì)列的執(zhí)行(比如用戶(hù)點(diǎn)擊沒(méi)有反應(yīng)的情況)囚霸;
說(shuō)到這里,你可能已經(jīng)有點(diǎn)凌亂了激才,在Dart的單線(xiàn)程中拓型,代碼到底是怎樣執(zhí)行的呢?
1贸营、Dart的入口是main函數(shù)吨述,所以
main函數(shù)中的代碼
會(huì)優(yōu)先執(zhí)行岩睁;2钞脂、main函數(shù)執(zhí)行完后,會(huì)啟動(dòng)一個(gè)事件循環(huán)(Event Loop)就會(huì)啟動(dòng)捕儒,啟動(dòng)后開(kāi)始執(zhí)行隊(duì)列中的任務(wù)冰啃;
3、首先刘莹,會(huì)按照先進(jìn)先出的順序阎毅,執(zhí)行
微任務(wù)隊(duì)列(Microtask Queue)
中的所有任務(wù);4点弯、其次扇调,會(huì)按照先進(jìn)先出的順序,執(zhí)行
事件隊(duì)列(Event Queue)
中的所有任務(wù)抢肛;
3.1.2. 如何創(chuàng)建微任務(wù)
在開(kāi)發(fā)中狼钮,我們可以通過(guò)dart中async下的scheduleMicrotask來(lái)創(chuàng)建一個(gè)微任務(wù):
import "dart:async";
main(List<String> args) {
scheduleMicrotask(() {
print("Hello Microtask");
});
}
在開(kāi)發(fā)中,如果我們有一個(gè)任務(wù)不希望它放在Event Queue中依次排隊(duì)捡絮,那么就可以創(chuàng)建一個(gè)微任務(wù)了熬芜。
Future的代碼是加入到事件隊(duì)列還是微任務(wù)隊(duì)列呢?
Future中通常有兩個(gè)函數(shù)執(zhí)行體:
Future構(gòu)造函數(shù)傳入的函數(shù)體
then的函數(shù)體(catchError等同看待)
那么它們是加入到什么隊(duì)列中的呢福稳?
Future構(gòu)造函數(shù)傳入的函數(shù)體放在事件隊(duì)列中
then的函數(shù)體要分成三種情況:
情況一:Future沒(méi)有執(zhí)行完成(有任務(wù)需要執(zhí)行)涎拉,那么then會(huì)直接被添加到Future的函數(shù)執(zhí)行體后;
情況二:如果Future執(zhí)行完后就then的圆,該then的函數(shù)體被放到如微任務(wù)隊(duì)列鼓拧,當(dāng)前Future執(zhí)行完后執(zhí)行微任務(wù)隊(duì)列;
情況三:如果Future是鏈?zhǔn)秸{(diào)用越妈,意味著then未執(zhí)行完季俩,下一個(gè)then不會(huì)執(zhí)行;
// future_1加入到eventqueue中叮称,緊隨其后then_1被加入到eventqueue中
Future(() => print("future_1")).then((_) => print("then_1"));
// Future沒(méi)有函數(shù)執(zhí)行體种玛,then_2被加入到microtaskqueue中
Future(() => null).then((_) => print("then_2"));
// future_3藐鹤、then_3_a、then_3_b依次加入到eventqueue中
Future(() => print("future_3")).then((_) => print("then_3_a")).then((_) => print("then_3_b"));
3.1.3. 代碼執(zhí)行順序
我們根據(jù)前面的規(guī)則來(lái)學(xué)習(xí)一個(gè)終極的代碼執(zhí)行順序
案例:
import "dart:async";
main(List<String> args) {
print("main start");
Future(() => print("task1"));
final future = Future(() => null);
Future(() => print("task2")).then((_) {
print("task3");
scheduleMicrotask(() => print('task4'));
}).then((_) => print("task5"));
future.then((_) => print("task6"));
scheduleMicrotask(() => print('task7'));
Future(() => print('task8'))
.then((_) => Future(() => print('task9')))
.then((_) => print('task10'));
print("main end");
}
代碼執(zhí)行的結(jié)果是:
main start
main end
task7
task1
task6
task2
task3
task5
task4
task8
task9
task10
代碼分析:
1赂韵、main函數(shù)先執(zhí)行娱节,所以
main start
和main end
先執(zhí)行,沒(méi)有任何問(wèn)題祭示;2肄满、main函數(shù)執(zhí)行
過(guò)程中
,會(huì)將一些任務(wù)分別加入到EventQueue
和MicrotaskQueue
中质涛;3稠歉、task7通過(guò)
scheduleMicrotask
函數(shù)調(diào)用,所以它被最早加入到MicrotaskQueue
汇陆,會(huì)被先執(zhí)行怒炸;4、然后開(kāi)始執(zhí)行
EventQueue
毡代,task1被添加到EventQueue
中被執(zhí)行阅羹;5、通過(guò)
final future = Future(() => null);
創(chuàng)建的future的then被添加到微任務(wù)中教寂,微任務(wù)直接被優(yōu)先執(zhí)行捏鱼,所以會(huì)執(zhí)行task6;6酪耕、一次在
EventQueue
中添加task2导梆、task3、task5被執(zhí)行迂烁;7看尼、task3的打印執(zhí)行完后,調(diào)用
scheduleMicrotask
婚被,那么在執(zhí)行完這次的EventQueue
后會(huì)執(zhí)行狡忙,所以在task5后執(zhí)行task4(注意:scheduleMicrotask
的調(diào)用是作為task3的一部分代碼,所以task4是要在task5之后執(zhí)行的)8址芯、task8灾茁、task9、task10一次添加到
EventQueue
被執(zhí)行谷炸;
事實(shí)上北专,上面的代碼執(zhí)行順序有可能出現(xiàn)在面試中,我們開(kāi)發(fā)中通常不會(huì)出現(xiàn)這種復(fù)雜的嵌套旬陡,并且需要完全搞清楚它的執(zhí)行順序拓颓;
但是,了解上面的代碼執(zhí)行順序描孟,會(huì)讓你對(duì)EventQueue
和microtaskQueue
有更加深刻的理解驶睦。
3.2. 多核CPU的利用
3.2.1. Isolate的理解
在Dart中砰左,有一個(gè)Isolate的概念,它是什么呢场航?
我們已經(jīng)知道Dart是單線(xiàn)程的缠导,這個(gè)線(xiàn)程有自己可以訪(fǎng)問(wèn)的內(nèi)存空間以及需要運(yùn)行的事件循環(huán);
我們可以將這個(gè)空間系統(tǒng)稱(chēng)之為是一個(gè)Isolate溉痢;
比如Flutter中就有一個(gè)Root Isolate僻造,負(fù)責(zé)運(yùn)行Flutter的代碼,比如UI渲染孩饼、用戶(hù)交互等等髓削;
在 Isolate 中,資源隔離做得非常好镀娶,每個(gè) Isolate 都有自己的 Event Loop 與 Queue立膛,
- Isolate 之間不共享任何資源,只能依靠消息機(jī)制通信汽畴,因此也就沒(méi)有資源搶占問(wèn)題旧巾。
但是耸序,如果只有一個(gè)Isolate忍些,那么意味著我們只能永遠(yuǎn)利用一個(gè)線(xiàn)程,這對(duì)于多核CPU來(lái)說(shuō)坎怪,是一種資源的浪費(fèi)罢坝。
如果在開(kāi)發(fā)中,我們有非常多耗時(shí)的計(jì)算搅窿,完全可以自己創(chuàng)建Isolate嘁酿,在獨(dú)立的Isolate中完成想要的計(jì)算操作。
如何創(chuàng)建Isolate呢男应?
創(chuàng)建Isolate是比較簡(jiǎn)單的闹司,我們通過(guò)Isolate.spawn
就可以創(chuàng)建了:
import "dart:isolate";
main(List<String> args) {
Isolate.spawn(foo, "Hello Isolate");
}
void foo(info) {
print("新的isolate:$info");
}
3.2.2. Isolate通信機(jī)制
但是在真實(shí)開(kāi)發(fā)中,我們不會(huì)只是簡(jiǎn)單的開(kāi)啟一個(gè)新的Isolate沐飘,而不關(guān)心它的運(yùn)行結(jié)果:
我們需要新的Isolate進(jìn)行計(jì)算游桩,并且將計(jì)算結(jié)果告知Main Isolate(也就是默認(rèn)開(kāi)啟的Isolate);
Isolate 通過(guò)發(fā)送管道(SendPort)實(shí)現(xiàn)消息通信機(jī)制耐朴;
我們可以在啟動(dòng)并發(fā)Isolate時(shí)將Main Isolate的發(fā)送管道作為參數(shù)傳遞給它借卧;
并發(fā)在執(zhí)行完畢時(shí),可以利用這個(gè)管道給Main Isolate發(fā)送消息筛峭;
import "dart:isolate";
main(List<String> args) async {
// 1.創(chuàng)建管道
ReceivePort receivePort= ReceivePort();
// 2.創(chuàng)建新的Isolate
Isolate isolate = await Isolate.spawn<SendPort>(foo, receivePort.sendPort);
// 3.監(jiān)聽(tīng)管道消息
receivePort.listen((data) {
print('Data:$data');
// 不再使用時(shí)铐刘,我們會(huì)關(guān)閉管道
receivePort.close();
// 需要將isolate殺死
isolate?.kill(priority: Isolate.immediate);
});
}
void foo(SendPort sendPort) {
sendPort.send("Hello World");
}
但是我們上面的通信變成了單向通信,如果需要雙向通信呢影晓?
事實(shí)上雙向通信的代碼會(huì)比較麻煩镰吵;
Flutter提供了支持并發(fā)計(jì)算的
compute
函數(shù)檩禾,它內(nèi)部封裝了Isolate的創(chuàng)建和雙向通信;利用它我們可以充分利用多核心CPU疤祭,并且使用起來(lái)也非常簡(jiǎn)單锌订;
注意:下面的代碼不是dart的API,而是Flutter的API画株,所以只有在Flutter項(xiàng)目中才能運(yùn)行
main(List<String> args) async {
int result = await compute(powerNum, 5);
print(result);
}
int powerNum(int num) {
return num * num;
}