Flutter(三)Dart的異步

一. 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 startmain end先執(zhí)行,沒(méi)有任何問(wèn)題祭示;

  • 2肄满、main函數(shù)執(zhí)行過(guò)程中,會(huì)將一些任務(wù)分別加入到EventQueueMicrotaskQueue中质涛;

  • 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ì)EventQueuemicrotaskQueue有更加深刻的理解驶睦。

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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辆飘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谓传,更是在濱河造成了極大的恐慌蜈项,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件续挟,死亡現(xiàn)場(chǎng)離奇詭異紧卒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)诗祸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)跑芳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人直颅,你說(shuō)我怎么就攤上這事博个。” “怎么了功偿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵盆佣,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我械荷,道長(zhǎng)共耍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任吨瞎,我火速辦了婚禮痹兜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颤诀。我一直安慰自己字旭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布着绊。 她就那樣靜靜地躺著谐算,像睡著了一般。 火紅的嫁衣襯著肌膚如雪归露。 梳的紋絲不亂的頭發(fā)上洲脂,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼恐锦。 笑死往果,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的一铅。 我是一名探鬼主播陕贮,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼潘飘!你這毒婦竟也來(lái)了肮之?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤卜录,失蹤者是張志新(化名)和其女友劉穎戈擒,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體艰毒,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筐高,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丑瞧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柑土。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绊汹,靈堂內(nèi)的尸體忽然破棺而出稽屏,到底是詐尸還是另有隱情,我是刑警寧澤灸促,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布诫欠,位于F島的核電站,受9級(jí)特大地震影響浴栽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轿偎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一典鸡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坏晦,春花似錦萝玷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仓蛆,卻和暖如春睁冬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背看疙。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工豆拨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留直奋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓施禾,卻偏偏與公主長(zhǎng)得像脚线,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子弥搞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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