Flutter開發(fā)(9)- Dart的異步

????在寫這篇文章之前,我一直在猶豫哆窿,要不要在這里講解Dart的異步相關(guān)話題农曲,因?yàn)檫@部分內(nèi)容很容易讓初學(xué)者望而卻步:

????1社搅、關(guān)于單線程和異步之間的關(guān)系,比較容易讓人迷惑乳规,不過我一定會(huì)用自己的方式盡可能讓你聽懂形葬。

????2、大量的異步操作方式(Future驯妄、await荷并、async等),目前你看不到具體的應(yīng)用場景青扔。(比如你學(xué)習(xí)過前端中的Promise源织、await、async可能會(huì)比較簡單微猖,但是我會(huì)假設(shè)你沒有這樣的基礎(chǔ))谈息。

????不過,聽我說:如果這一個(gè)章節(jié)你學(xué)完之后還有很多疑惑凛剥,沒有關(guān)系侠仇,在后面用到相關(guān)知識(shí)時(shí),回頭來看犁珠,你會(huì)豁然開朗逻炊。

一. Dart的異步模型

我們先來搞清楚Dart是如何搞定異步操作的

1.1. Dart是單線程的

1.1.1. 程序中的耗時(shí)操作

開發(fā)中的耗時(shí)操作:

????在開發(fā)中,我們經(jīng)常會(huì)遇到一些耗時(shí)的操作需要完成犁享,比如網(wǎng)絡(luò)請求余素、文件讀取等等;

????如果我們的主線程一直在等待這些耗時(shí)的操作完成炊昆,那么就會(huì)進(jìn)行阻塞桨吊,無法響應(yīng)其它事件威根,比如用戶的點(diǎn)擊;

????顯然视乐,我們不能這么干B宀蟆!

如何處理耗時(shí)的操作呢佑淀?

????針對(duì)如何處理耗時(shí)的操作留美,不同的語言有不同的處理方式。

????處理方式一: 多線程渣聚,比如Java独榴、C++,我們普遍的做法是開啟一個(gè)新的線程(Thread)奕枝,在新的線程中完成這些異步的操作棺榔,再通過線程間通信的方式,將拿到的數(shù)據(jù)傳遞給主線程隘道。

????處理方式二: 單線程+事件循環(huán)症歇,比如JavaScript、Dart都是基于單線程加事件循環(huán)來完成耗時(shí)操作的處理谭梗。不過單線程如何能進(jìn)行耗時(shí)的操作呢忘晤?!

1.1.2. 單線程的異步操作

????我之前碰到很多開發(fā)者都對(duì)單線程的異步操作充滿了問號(hào)激捏?设塔??

其實(shí)它們并不沖突:

????因?yàn)槲覀兊囊粋€(gè)應(yīng)用程序大部分時(shí)間都是處于空閑的狀態(tài)的远舅,并不是無限制的在和用戶進(jìn)行交互闰蛔。

????比如等待用戶點(diǎn)擊、網(wǎng)絡(luò)請求數(shù)據(jù)的返回图柏、文件讀寫的IO操作序六,這些等待的行為并不會(huì)阻塞我們的線程;

????這是因?yàn)轭愃朴诰W(wǎng)絡(luò)請求蚤吹、文件讀寫的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)前線程會(huì)被掛起,調(diào)用線程只有在得到調(diào)用結(jié)果之后才會(huì)繼續(xù)執(zhí)行诸蚕。

????非阻塞式調(diào)用: 調(diào)用執(zhí)行之后步势,當(dāng)前線程不會(huì)停止執(zhí)行,只需要過一段時(shí)間來檢查一下有沒有結(jié)果返回即可背犯。

我們用一個(gè)生活中的例子來模擬:

????你中午餓了坏瘩,需要點(diǎn)一份外賣,點(diǎn)外賣的動(dòng)作就是我們的調(diào)用漠魏,拿到最后點(diǎn)的外賣就是我們要等待的結(jié)果倔矾。

????阻塞式調(diào)用: 點(diǎn)了外賣,不再做任何事情柱锹,就是在傻傻的等待哪自,你的線程停止了任何其他的工作。

????非阻塞式調(diào)用: 點(diǎn)了外賣禁熏,繼續(xù)做其他事情:繼續(xù)工作壤巷、打把游戲,你的線程沒有繼續(xù)執(zhí)行其他事情瞧毙,只需要偶爾去看一下有沒有人敲門胧华,外賣有沒有送到即可。

而我們開發(fā)中的很多耗時(shí)操作宙彪,都可以基于這樣的 非阻塞式調(diào)用:

????比如網(wǎng)絡(luò)請求本身使用了Socket通信矩动,而Socket本身提供了select模型,可以進(jìn)行非阻塞方式的工作释漆;

????比如文件讀寫的IO操作悲没,我們可以使用操作系統(tǒng)提供的基于事件的回調(diào)機(jī)制;

這些操作都不會(huì)阻塞我們單線程的繼續(xù)執(zhí)行男图,我們的線程在等待的過程中可以繼續(xù)去做別的事情:喝杯咖啡示姿、打把游戲,等真正有了響應(yīng)享言,再去進(jìn)行對(duì)應(yīng)的處理即可峻凫。

這時(shí),我們可能有兩個(gè)問題:

????問題一: 如果在多核CPU中览露,單線程是不是就沒有充分利用CPU呢荧琼?這個(gè)問題,我會(huì)放在后面來講解差牛。

????問題二: 單線程是如何來處理網(wǎng)絡(luò)通信命锄、IO操作它們返回的結(jié)果呢?答案就是事件循環(huán)(Event Loop)偏化。

1.2. Dart事件循環(huán)

1.2.1. 什么是事件循環(huá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ì)列清空位置。

我們來寫一個(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ì)列中一次取出事件來執(zhí)行痹扇。

1.2.2. 事件循環(huán)代碼模擬

這里我們來看一段偽代碼,理解點(diǎn)擊事件和網(wǎng)絡(luò)請求的事件是如何被執(zhí)行的:

????這是一段Flutter代碼溯香,很多東西大家可能不是特別理解鲫构,但是耐心閱讀你會(huì)讀懂我們在做什么。

????一個(gè)按鈕RaisedButton玫坛,當(dāng)發(fā)生點(diǎn)擊時(shí)執(zhí)行onPressed函數(shù)结笨。

????onPressed函數(shù)中,我們發(fā)送了一個(gè)網(wǎng)絡(luò)請求湿镀,請求成功后會(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)用戶發(fā)生點(diǎn)擊的時(shí)候赫模,onPressed回調(diào)函數(shù)被放入事件循環(huán)中執(zhí)行,執(zhí)行的過程中發(fā)送了一個(gè)網(wǎng)絡(luò)請求蒸矛。

????2瀑罗、網(wǎng)絡(luò)請求發(fā)出去后,該事件循環(huán)不會(huì)被阻塞雏掠,而是發(fā)現(xiàn)要執(zhí)行的onPressed函數(shù)已經(jīng)結(jié)束斩祭,會(huì)將它丟棄掉。

????3乡话、網(wǎng)絡(luò)請求成功后摧玫,會(huì)執(zhí)行then中傳入的回調(diào)函數(shù),這也是一個(gè)事件绑青,該事件被放入到事件循環(huán)中執(zhí)行诬像,執(zhí)行完畢后屋群,事件循環(huán)將其丟棄。

盡管onPressed和then中的回調(diào)有一些差異坏挠,但是它們對(duì)于事件循環(huán)來說谓晌,都是告訴它:我有一段代碼需要執(zhí)行,快點(diǎn)幫我完成癞揉。

二. Dart的異步操作

????Dart中的異步操作主要使用Future以及async、await溺欧。

????如果你之前有過前端的ES6喊熟、ES7編程經(jīng)驗(yàn),那么完全可以將Future理解成Promise姐刁,async芥牌、await和ES7中基本一致。

????但是如果沒有前端開發(fā)經(jīng)驗(yàn)聂使,F(xiàn)uture以及async壁拉、await如何理解呢?

2.1. 認(rèn)識(shí)Future

????我思考了很久柏靶,這個(gè)Future到底應(yīng)該如何講解

2.1.1. 同步的網(wǎng)絡(luò)請求

我們先來看一個(gè)例子吧:

????在這個(gè)例子中弃理,我使用getNetworkData來模擬了一個(gè)網(wǎng)絡(luò)請求;

????該網(wǎng)絡(luò)請求需要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ò)請求阻塞了main函數(shù),那么意味著其后所有的代碼都無法正常的繼續(xù)執(zhí)行扼劈。

2.1.2. 異步的網(wǎng)絡(luò)請求

我們來對(duì)我們上面的代碼進(jìn)行改進(jìn)驻啤,代碼如下:

????和剛才的代碼唯一的區(qū)別在于我使用了Future對(duì)象來將耗時(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";

? });

}

我們來看一下代碼的運(yùn)行結(jié)果:

????1先煎、這一次的代碼順序執(zhí)行沐旨,沒有出現(xiàn)任何的阻塞現(xiàn)象;

????2榨婆、和之前直接打印結(jié)果不同磁携,這次我們打印了一個(gè)Future實(shí)例;

????結(jié)論:我們將一個(gè)耗時(shí)的操作隔離了起來良风,這個(gè)操作不會(huì)再影響我們的主線程執(zhí)行了谊迄。

????問題:我們?nèi)绾稳ツ玫阶罱K的結(jié)果呢闷供?

main function start

Instance of 'Future<String>'

main function end

獲取Future得到的結(jié)果

有了Future之后,如何去獲取請求到的結(jié)果:通過.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)用過程中出現(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ò)請求出現(xiàn)錯(cuò)誤");

? });

}

上面代碼的執(zhí)行結(jié)果:

main function start

Instance of 'Future<String>'

main function end

// 3s后沒有拿到結(jié)果啄寡,但是我們捕獲到了異常

Exception: 網(wǎng)絡(luò)請求出現(xiàn)錯(cuò)誤

2.1.3. Future使用補(bǔ)充

補(bǔ)充一:上面案例的小結(jié)

我們通過一個(gè)案例來學(xué)習(xí)了一些Future的使用過程:

????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识藤、通過.then(成功回調(diào)函數(shù))的方式來監(jiān)聽Future內(nèi)部執(zhí)行完成時(shí)獲取到的結(jié)果砚著;

????3、通過.catchError(失敗或異吵彰粒回調(diào)函數(shù))的方式來監(jiān)聽Future內(nèi)部執(zhí)行失敗或者出現(xiàn)異常時(shí)的錯(cuò)誤信息稽穆;

補(bǔ)充二:Future的兩種狀態(tài)

事實(shí)上Future在執(zhí)行的整個(gè)過程中,我們通常把它劃分成了兩種狀態(tài):

狀態(tài)一:未完成狀態(tài)(uncompleted)

????執(zhí)行Future內(nèi)部的操作時(shí)(在上面的案例中就是具體的網(wǎng)絡(luò)請求過程赶撰,我們使用了延遲來模擬)秧骑,我們稱這個(gè)過程為未完成狀態(tài)

狀態(tài)二:完成狀態(tài)(completed)

????當(dāng)Future內(nèi)部的操作執(zhí)行完成,通常會(huì)返回一個(gè)值扣囊,或者拋出一個(gè)異常乎折。

????這兩種情況,我們都稱Future為完成狀態(tài)侵歇。

Dart官網(wǎng)有對(duì)這兩種狀態(tà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);

????之前的案例,我們也可以使用它來模擬紊遵,但是直接學(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)該沒有什么難度。

await学搜、async是什么呢娃善?

????它們是Dart中的關(guān)鍵字(你這不是廢話嗎?廢話也還是要強(qiáng)調(diào)的瑞佩,萬一你用它做變量名呢聚磺,無辜臉。)

????它們可以讓我們用同步的代碼格式钉凌,去實(shí)現(xiàn)異步的調(diào)用過程。

????并且捂人,通常一個(gè)async的函數(shù)會(huì)返回一個(gè)Future(別著急御雕,馬上就看到代碼了)。

我們已經(jīng)知道滥搭,F(xiàn)uture可以做到不阻塞我們的線程酸纲,讓線程繼續(xù)執(zhí)行,并且在完成某個(gè)操作時(shí)改變自己的狀態(tài)瑟匆,并且回調(diào)then或者errorCatch回調(diào)闽坡。

如何生成一個(gè)Future呢?

????1愁溜、通過我們前面學(xué)習(xí)的Future構(gòu)造函數(shù)疾嗅,或者后面學(xué)習(xí)的Future其他API都可以。

????2冕象、還有一種就是通過async的函數(shù)。

2.2.2. 案例代碼演練

Talk is cheap. Show me the code.

我們來對(duì)之前的Future異步處理代碼進(jìn)行改造,改成await晒杈、async的形式计贰。

我們知道,如果直接這樣寫代碼墓律,代碼是不能正常執(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? "請求到的數(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? "請求到的數(shù)據(jù):" + result;

}

修改后執(zhí)行代碼笤昨,會(huì)看到如下的錯(cuò)誤:

????錯(cuò)誤非常明顯:await關(guān)鍵字必須存在于async函數(shù)中。

????所以我們需要將getNetworkData函數(shù)定義成async函數(shù)握恳。

繼續(xù)修改代碼如下:

????也非常簡單瞒窒,只需要在函數(shù)的()后面加上一個(gè)async關(guān)鍵字就可以了

String getNetworkData() async {

? var result = await Future.delayed(Duration(seconds: 3), () {

? ? return "network data";

? });

? return? "請求到的數(shù)據(jù):" + result;

}

運(yùn)行代碼,依然報(bào)錯(cuò)(心想:你妹跋缤荨):

????錯(cuò)誤非常明顯:使用async標(biāo)記的函數(shù)崇裁,必須返回一個(gè)Future對(duì)象。

????所以我們需要繼續(xù)修改代碼束昵,將返回值寫成一個(gè)Future拔稳。

繼續(xù)修改代碼如下:

Future<String> getNetworkData() async {

? var result = await Future.delayed(Duration(seconds: 3), () {

? ? return "network data";

? });

? return "請求到的數(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è)參考奸远,我并不打算展開來講,因?yàn)樾枰玫紽lutter的相關(guān)知識(shí)讽挟;

????后面我會(huì)在后面的案例中再次講解它在Flutter中我使用的過程中懒叛;????

讀取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類型

? 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)來執(zhí)行我們的代碼芍瑞,里面存在一個(gè)事件隊(duì)列(Event Queue),事件循環(huán)不斷從事件隊(duì)列中取出事件執(zhí)行褐墅。

但是如果我們嚴(yán)格來劃分的話拆檬,在Dart中還存在另一個(gè)隊(duì)列:微任務(wù)隊(duì)列(Microtask Queue)。

????微任務(wù)隊(duì)列的優(yōu)先級(jí)要高于事件隊(duì)列妥凳;

????也就是說事件循環(huán)都是優(yōu)先執(zhí)行微任務(wù)隊(duì)列中的任務(wù)竟贯,再執(zhí)行?事件隊(duì)列?中的任務(wù);

那么在Flutter開發(fā)中逝钥,哪些是放在事件隊(duì)列屑那,哪些是放在微任務(wù)隊(duì)列呢拱镐?

????所有的外部事件任務(wù)都在事件隊(duì)列中,如IO持际、計(jì)時(shí)器沃琅、點(diǎn)擊、以及繪制事件等蜘欲;

????而微任務(wù)通常來源于Dart內(nèi)部益眉,并且微任務(wù)非常少。這是因?yàn)槿绻⑷蝿?wù)非常多姥份,就會(huì)造成事件隊(duì)列排不上隊(duì)郭脂,會(huì)阻塞任務(wù)隊(duì)列的執(zhí)行(比如用戶點(diǎn)擊沒有反應(yīng)的情況);

說道這里澈歉,你可能已經(jīng)有點(diǎn)凌亂了展鸡,在Dart的單線程中,代碼到底是怎樣執(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)后開始執(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ù)

????在開發(fā)中香拉,我們可以通過dart中async下的scheduleMicrotask來創(chuàng)建一個(gè)微任務(wù):

????import "dart:async";

????main(List<String> args) {??

????????scheduleMicrotask(() {? ??

????????????print("Hello Microtask");? });}

在開發(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沒有執(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沒有函數(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ī)則來學(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í)行营搅,沒有任何問題;

????2园欣、main函數(shù)執(zhí)行過程中俊庇,會(huì)將一些任務(wù)分別加入到EventQueue和MicrotaskQueue中;

????3搬男、task7通過scheduleMicrotask函數(shù)調(diào)用彭沼,所以它被最早加入到MicrotaskQueue姓惑,會(huì)被先執(zhí)行;

????4敦冬、然后開始執(zhí)行EventQueue唯沮,task1被添加到EventQueue中被執(zhí)行介蛉;

????5、通過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)在面試中舅世,我們開發(fā)中通常不會(huì)出現(xiàn)這種復(fù)雜的嵌套,并且需要完全搞清楚它的執(zhí)行順序缨硝;

但是罢低,了解上面的代碼執(zhí)行順序网持,會(huì)讓你對(duì)EventQueue和microtaskQueue有更加深刻的理解功舀。

3.2. 多核CPU的利用

3.2.1. Isolate的理解

在Dart中,有一個(gè)Isolate的概念列敲,它是什么呢?

????我們已經(jīng)知道Dart是單線程的瘫絮,這個(gè)線程有自己可以訪問的內(nèi)存空間以及需要運(yùn)行的事件循環(huán)麦萤;

????我們可以將這個(gè)空間系統(tǒng)稱之為是一個(gè)Isolate扁眯;

????比如Flutter中就有一個(gè)Root Isolate姻檀,負(fù)責(zé)運(yùn)行Flutter的代碼绣版,比如UI渲染、用戶交互等等诈唬;

在 Isolate 中缩麸,資源隔離做得非常好杭朱,每個(gè) Isolate 都有自己的 Event Loop 與 Queue弧械,

????Isolate 之間不共享任何資源,只能依靠消息機(jī)制通信羞迷,因此也就沒有資源搶占問題闭树。

但是荒澡,如果只有一個(gè)Isolate单山,那么意味著我們只能永遠(yuǎn)利用一個(gè)線程,這對(duì)于多核CPU來說昼接,是一種資源的浪費(fèi)慢睡。

如果在開發(fā)中铡溪,我們有非常多耗時(shí)的計(jì)算棕硫,完全可以自己創(chuàng)建Isolate,在獨(dú)立的Isolate中完成想要的計(jì)算操作纬纪。

如何創(chuàng)建Isolate呢包各?

創(chuàng)建Isolate是比較簡單的髓棋,我們通過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í)開發(fā)中惶洲,我們不會(huì)只是簡單的開啟一個(gè)新的Isolate恬吕,而不關(guān)心它的運(yùn)行結(jié)果:

????我們需要新的Isolate進(jìn)行計(jì)算铐料,并且將計(jì)算結(jié)果告知Main Isolate(也就是默認(rèn)開啟的Isolate);

????Isolate 通過發(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)聽管道消息

? 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,并且使用起來也非常簡單闲坎;

注意:下面的代碼不是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;

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末怖喻,一起剝皮案震驚了整個(gè)濱河市岁诉,隨后出現(xiàn)的幾起案子涕癣,更是在濱河造成了極大的恐慌,老刑警劉巖候生,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異须蜗,居然都是意外死亡明肮,警方通過查閱死者的電腦和手機(jī)柿估,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門秫舌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來足陨,“玉大人,你說我怎么就攤上這事星虹】碛浚” “怎么了护糖?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵嫡良,是天一觀的道長献酗。 經(jīng)常有香客問我罕偎,道長,這世上最難降的妖魔是什么蹂楣? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任痊土,我火速辦了婚禮赁酝,結(jié)果婚禮上旭等,老公的妹妹穿的比我還像新娘搔耕。我一直安慰自己弃榨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著划咐,像睡著了一般钧萍。 火紅的嫁衣襯著肌膚如雪风瘦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音昧谊,去河邊找鬼酗捌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛阀圾,可吹牛的內(nèi)容都是我干的初烘。 我是一名探鬼主播敞曹,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼橄登!你這毒婦竟也來了讥此?” 一聲冷哼從身側(cè)響起萄喳,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤他巨,失蹤者是張志新(化名)和其女友劉穎染突,沒想到半個(gè)月后份企,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年囚霸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邮辽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吨述。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揣云,死狀恐怖邓夕,靈堂內(nèi)的尸體忽然破棺而出焚刚,到底是詐尸還是另有隱情,我是刑警寧澤抢肛,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站莲镣,受9級(jí)特大地震影響瑞侮,放射性物質(zhì)發(fā)生泄漏区岗。R本人自食惡果不足惜慈缔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一藐鹤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挠蛉,春花似錦谴古、人聲如沸掰担。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至执庐,卻和暖如春酪耕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耕肩。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工因妇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留问潭,地道東北人猿诸。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像狡忙,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子灾茁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容