Dart 異步編程

1弯囊、 isolate機(jī)制

Dart是基于單線程模型的語言。但是在開發(fā)當(dāng)中我們經(jīng)常會進(jìn)行耗時操作比如網(wǎng)絡(luò)請求,這種耗時操作會堵塞我們的代碼寡夹,所以在Dart也有并發(fā)機(jī)制,名叫isolate厂置。APP的啟動入口main函數(shù)就是一個類似Android主線程的一個主isolate菩掏。和Java的Thread不同的是,Dart中的isolate無法共享內(nèi)存昵济。

圖片.png
import 'dart:isolate';

int i;

void main() {
  i = 10;
  //創(chuàng)建一個消息接收器
  ReceivePort receivePort = new ReceivePort();
  //創(chuàng)建isolate
  Isolate.spawn(isolateMain, receivePort.sendPort);

  //接收其他isolate發(fā)過來的消息
  receivePort.listen((message) {
    //發(fā)過來sendPort,則主isolate也可以向創(chuàng)建的isolate發(fā)送消息
    if (message is SendPort) {
      message.send("好呀好呀!");
    } else {
      print("接到子isolate消息:" + message);
    }
  });
}

/// 新isolate的入口函數(shù)
void isolateMain(SendPort sendPort) {
  // isolate是內(nèi)存隔離的智绸,i的值是在主isolate定義的所以這里獲得null
  print(i);

  ReceivePort receivePort = new ReceivePort();
  sendPort.send(receivePort.sendPort);


  // 向主isolate發(fā)送消息
  sendPort.send("去大保健嗎?");


  receivePort.listen((message) {
    print("接到主isolate消息:" + message);
  });
}

2访忿、 event-loop

可以看到代碼中瞧栗,我們接收消息使用了listene函數(shù)來監(jiān)聽消息。假設(shè)我們現(xiàn)在在main方法最后加入sleep休眠海铆,會不會影響listene回調(diào)的時機(jī)迹恐?

import 'dart:io';
import 'dart:isolate';

int i;

void main() {
  i = 10;
  //創(chuàng)建一個消息接收器
  ReceivePort receivePort = new ReceivePort();
  //創(chuàng)建isolate
  Isolate.spawn(isolateMain, receivePort.sendPort);

  //接收其他isolate發(fā)過來的消息
  receivePort.listen((message) {
    //發(fā)過來sendPort,則主isolate也可以向創(chuàng)建的isolate發(fā)送消息
    if (message is SendPort) {
      message.send("好呀好呀!");
    } else {
      print("接到子isolate消息:" + message);
    }
  });

  //增加休眠,是否會影響listen的時機(jī)卧斟?
  sleep(Duration(seconds: 2));
  print("休眠完成");
}

/// 新isolate的入口函數(shù)
void isolateMain(SendPort sendPort) {
  // isolate是內(nèi)存隔離的殴边,i的值是在主isolate定義的所以這里獲得null
  print(i);

  ReceivePort receivePort = new ReceivePort();
  sendPort.send(receivePort.sendPort);
  // 向主isolate發(fā)送消息
  sendPort.send("去大保健嗎?");


  receivePort.listen((message) {
    print("接到主isolate消息:" + message);
  });
}
結(jié)果是大概2s后珍语,我們的`listene`才打印出其他isolate發(fā)過來的消息锤岸。同Android Handler類似,在Dart運(yùn)行環(huán)境中也是靠事件驅(qū)動的板乙,通過event loop不停的從隊(duì)列中獲取消息或者事件來驅(qū)動整個應(yīng)用的運(yùn)行是偷,isolate發(fā)過來的消息就是通過loop處理。但是不同的是在Android中每個線程只有一個Looper所對應(yīng)的MessageQueue亡驰,而Dart中有兩個隊(duì)列,一個叫做**event queue(事件隊(duì)列)**饿幅,另一個叫做**microtask queue(微任務(wù)隊(duì)列)**凡辱。
圖片.png
Dart在執(zhí)行完main函數(shù)后,就會由Loop開始執(zhí)行兩個任務(wù)隊(duì)列中的Event栗恩。首先Loop檢查微服務(wù)隊(duì)列透乾,依次執(zhí)行Event,當(dāng)微服務(wù)隊(duì)列執(zhí)行完后磕秤,就檢查Event queue隊(duì)列依次執(zhí)行乳乌,在執(zhí)行Event queue的過程中,沒執(zhí)行完一個Event就再檢查一次微服務(wù)隊(duì)列市咆。所以微服務(wù)隊(duì)列優(yōu)先級高汉操,可以利用微服務(wù)進(jìn)行插隊(duì)。

我們先來看個例子:


import 'dart:io';

void main(){
  new File("/Users/enjoy/a.txt").readAsString().then((content){
      print(content);
  });
  while(true){}
}

文件內(nèi)容永遠(yuǎn)也無法打印出來蒙兰,因?yàn)閙ain函數(shù)還沒執(zhí)行完磷瘤。而then方法是由Loop檢查Event queue執(zhí)行的芒篷。

如果需要往微服務(wù)中插入Event進(jìn)行插隊(duì):


import 'dart:async';
import 'dart:io';
//結(jié)果是限制性了microtask然后執(zhí)行then方法。
void main(){
  new File("/Users/enjoy/a.txt").readAsString().then((content){
      print(content);
  });
  //future內(nèi)部就是調(diào)用了 scheduleMicrotask
  Future.microtask((){
    print("future: excute microtask");

  });
//  scheduleMicrotask((){
//    print("");
//  });

}

3采缚、 Future

在 Dart 庫中隨處可見 Future 對象针炉,通常異步函數(shù)返回的對象就是一個 Future。 當(dāng)一個 future 執(zhí)行完后扳抽,他里面的值 就可以使用了篡帕,可以使用 then() 來在 future 完成的時候執(zhí)行其他代碼。Future對象其實(shí)就代表了在事件隊(duì)列中的一個事件的結(jié)果贸呢。

4镰烧、 異常

//當(dāng)給到一個不存在的文件地址時會發(fā)生異常,這時候可以利用catchError捕獲此異常贮尉。
//then().catchError() 模式就是異步的 try-catch拌滋。
new File("/Users/enjoy/a1.txt").readAsString().then((content) {
    print(content);
  }).catchError((e, s) {
    print(s);
  });

5、 組合

then()的返回值同樣是一個future對象猜谚,可以利用隊(duì)列的原理進(jìn)行組合異步任務(wù)

  new File("/Users/enjoy/a.txt").readAsString().then((content) {
    print(content);
    return 1; //1被轉(zhuǎn)化為 Future<int>類型 返回
  }).then((i){
    print(i);
  }).catchError((e, s) {
    print(s);
  });

上面的方式是等待執(zhí)行完成讀取文件之后败砂,再執(zhí)行一個新的future。如果我們需要等待一組任務(wù)都執(zhí)行完成再統(tǒng)一處理一些事情魏铅,可以通過wait()完成昌犹。

 Future readDone = new File("/Users/enjoy/a.txt").readAsString();
  //延遲3s
  Future delayedDone = Future.delayed(Duration(seconds: 3));

  Future.wait([readDone, delayedDone]).then((values) {
     print(values[0]);//第一個future的結(jié)果
     print(values[1]);//第二個future的結(jié)果
  });

6、 Stream

Stream(流) 在 Dart API 中也經(jīng)常出現(xiàn)览芳,表示發(fā)出的一系列的異步數(shù)據(jù)斜姥。 Stream 是一個異步數(shù)據(jù)源,它是 Dart 中處理異步事件流的統(tǒng)一 API沧竟。

Future 表示稍后獲得的一個數(shù)據(jù)铸敏,所有異步的操作的返回值都用 Future 來表示。但是 Future 只能表示一次異步獲得的數(shù)據(jù)悟泵。而 Stream 表示多次異步獲得的數(shù)據(jù)杈笔。比如 IO 處理的時候,每次只會讀取一部分?jǐn)?shù)據(jù)和一次性讀取整個文件的內(nèi)容相比糕非,Stream 的好處是處理過程中內(nèi)存占用較小蒙具。而 File 的 readAsString()是一次性讀取整個文件的內(nèi)容進(jìn)來,雖然獲得完整內(nèi)容處理起來比較方便朽肥,但是如果文件很大的話就會導(dǎo)致內(nèi)存占用過大的問題禁筏。

 new File("/Users/enjoy/app-release.apk").openRead().listen((List<int> bytes) {
    print("stream執(zhí)行"); //執(zhí)行多次
  });

  new File("/Users/enjoy/app-release.apk").readAsBytes().then((_){
    print("future執(zhí)行"); //執(zhí)行1次
  });

listen()其實(shí)就是訂閱這個Stream,它會返回一個StreamSubscription訂閱者衡招。訂閱者肯定就提供了取消訂閱的cancel()篱昔,去掉后我們的listen中就接不到任何信息了。除了cancel()取消方法之外始腾,我們還可以使用onData()重置listene方法旱爆,onDone監(jiān)聽完成等等操作舀射。

  StreamSubscription<List<int>> listen = new File("/Users/enjoy/app-release.apk").openRead().listen((List<int> bytes) {
    print("stream執(zhí)行");
  });
  listen.onData((_){
    print("替代listene");
  });
  listen.onDone((){
    print("結(jié)束");
  });
  listen.onError((e,s){
    print("異常");
  });
  //暫停,如果沒有繼續(xù)則會退出程序
  listen.pause();
  //繼續(xù)
  listen.resume();

7怀伦、 廣播模式

Stream有兩種訂閱模式:單訂閱和多訂閱脆烟。單訂閱就是只能有一個訂閱者,上面的使用我們都是單訂閱模式房待,而廣播是可以有多個訂閱者邢羔。通過 Stream.asBroadcastStream() 可以將一個單訂閱模式的 Stream 轉(zhuǎn)換成一個多訂閱模式的 Stream,isBroadcast 屬性可以判斷當(dāng)前 Stream 所處的模式桑孩。

var stream = new File("/Users/enjoy/app-release.apk").openRead();
  stream.listen((List<int> bytes) {
  });
  //錯誤 單訂閱只能有一個訂閱者
//  stream.listen((_){
//    print("stream執(zhí)行");
//  });

  var broadcastStream = new File("/Users/enjoy/app-release.apk").openRead().asBroadcastStream();
  broadcastStream.listen((_){
    print("訂閱者1");
  });
  broadcastStream.listen((_){
    print("訂閱者2");
  });

需要注意的是拜鹤,多訂閱模式如果沒有及時添加訂閱者則可能丟數(shù)據(jù)。

//默認(rèn)是單訂閱
  var stream = Stream.fromIterable([1, 2, 3]);
  //3s后添加訂閱者 不會丟失數(shù)據(jù)
  new Timer(new Duration(seconds: 3), () => stream.listen(print));

  //創(chuàng)建一個流管理器 對一個stream進(jìn)行管理
  var streamController = StreamController.broadcast();
  //添加
  streamController.add(1);
  //先發(fā)出事件再訂閱 無法接到通知
  streamController.stream.listen((i){
    print("broadcast:$i");
  });
  //記得關(guān)閉
  streamController.close();


  //這里沒有丟失流椒,因?yàn)閟tream通過asBroadcastStream轉(zhuǎn)為了多訂閱敏簿,但是本質(zhì)是單訂閱流,并不改變原始 stream 的實(shí)現(xiàn)特性
  var broadcastStream = Stream.fromIterable([1, 2, 3]).asBroadcastStream();
  new Timer(new Duration(seconds: 3), () => broadcastStream.listen(print));

8宣虾、 async/await

使用asyncawait的代碼是異步的惯裕,但是看起來很像同步代碼。當(dāng)我們需要獲得A的結(jié)果绣硝,再執(zhí)行B蜻势,時,你需要then()->then(),但是利用asyncawait能夠非常好的解決回調(diào)地獄的問題:

//async 表示這是一個異步方法,await必須再async方法中使用
//異步方法只能返回 void和Future
Future<String> readFile() async {
  //await 等待future執(zhí)行完成再執(zhí)行后續(xù)代碼
  String content = await new File("/Users/xiang/enjoy/a.txt").readAsString();
  String content2 = await new File("/Users/xiang/enjoy/a.txt").readAsString();
  //自動轉(zhuǎn)換為 future
  return content;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹉胖,一起剝皮案震驚了整個濱河市握玛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌甫菠,老刑警劉巖挠铲,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寂诱,居然都是意外死亡拂苹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門刹衫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醋寝,“玉大人搞挣,你說我怎么就攤上這事带迟。” “怎么了囱桨?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵仓犬,是天一觀的道長。 經(jīng)常有香客問我舍肠,道長搀继,這世上最難降的妖魔是什么窘面? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮叽躯,結(jié)果婚禮上财边,老公的妹妹穿的比我還像新娘。我一直安慰自己点骑,他們只是感情好酣难,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著黑滴,像睡著了一般憨募。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袁辈,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天菜谣,我揣著相機(jī)與錄音,去河邊找鬼晚缩。 笑死尾膊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的橡羞。 我是一名探鬼主播眯停,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卿泽!你這毒婦竟也來了莺债?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤签夭,失蹤者是張志新(化名)和其女友劉穎齐邦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體第租,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡措拇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了慎宾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丐吓。...
    茶點(diǎn)故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖趟据,靈堂內(nèi)的尸體忽然破棺而出券犁,到底是詐尸還是另有隱情,我是刑警寧澤汹碱,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布粘衬,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏稚新。R本人自食惡果不足惜勘伺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望褂删。 院中可真熱鬧飞醉,春花似錦、人聲如沸屯阀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹲盘。三九已至股毫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間召衔,已是汗流浹背铃诬。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苍凛,地道東北人趣席。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像醇蝴,于是被迫代替她去往敵國和親宣肚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評論 2 349

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

  • isolate Dart是基于單線程模型的語言悠栓。但是我們在開發(fā)中也會有請求網(wǎng)絡(luò)這樣的耗時操作霉涨,所以Dart也有并發(fā)...
    AilurusFulgens閱讀 210評論 0 0
  • dart是單線程 一定要記得很清楚dart異步不同于java的線程,java線程是搶占式惭适,但dart相當(dāng)于開辟...
    淹死丶的魚閱讀 3,398評論 1 1
  • Dart之異步編程 本文轉(zhuǎn)載自https://www.cnblogs.com/lxlx1798/p/1112656...
    緣煥閱讀 1,315評論 0 1
  • Dart異步編程學(xué)習(xí)札記 Dart是一門單線程笙瑟,所以在開發(fā)中我們不需要像其他多線程語言一樣需要考慮資源競爭問題,也...
    iOSer_jia閱讀 425評論 0 0
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險厭惡者癞志,不喜歡去冒險往枷,但是人生放棄了冒險,也就放棄了無數(shù)的可能凄杯。 ...
    yichen大刀閱讀 6,041評論 0 4