Dart學(xué)習(xí)4-異步編程

在dart中不存在線程的概念嚼隘,也就沒有鎖類似的概念烂斋。如果有耗時(shí)操作辽俗,就需要引入Isolate機(jī)制

1 Isolate機(jī)制

void main() {
  // Isolate有一個(gè)靜態(tài)方法。傳入的第一個(gè)參數(shù)是一個(gè)方法拴魄,這邊使用的匿名方法冗茸。第二個(gè)參數(shù)就是傳入方法的參數(shù)
  Isolate.spawn((message) {
    // 這里執(zhí)行的代碼都是子isolate中
    sleep(const Duration(seconds: 10));
  }, "我說從isolate傳遞到子isolate的數(shù)據(jù)");
}

那Isolate之間是怎么交互的呢?主isolate和子isolate的資源不共享

void main() {
  // 創(chuàng)建一個(gè)消息接收器
  var mainReceivePort = ReceivePort();
  // 從消息接收器中讀取消息
  mainReceivePort.listen((message) {
    // 這里接收從子isolate發(fā)來的消息
    print("主isolate接收器接收到消息:$message");

    // 如果消息類型是SendPort羹铅,則說明是發(fā)送器蚀狰。
    if (message is SendPort) {
      message.send("我是從主isolate發(fā)送到子isolate的消息");
    }
  });

  mainReceivePort.sendPort.send("我是從主isolate發(fā)來的消息");

  // 創(chuàng)建一個(gè)子isolate
  Isolate.spawn((SendPort mainSendPort) {
    // 用主isolate發(fā)來的發(fā)送器發(fā)送消息就能夠在主isolate中接收到消息
    mainSendPort.send("我是從子isolate發(fā)送到主isolate的消息");

    // 如果我們需要從主isolate向子isolate中發(fā)送消息,那就需要在子isolate中創(chuàng)建一個(gè)自己的接收器
    var childReceivePort = ReceivePort();
    // 同樣的职员,給子isolate中的接收器設(shè)置監(jiān)聽
    childReceivePort.listen((message) {
      print("子isolate接收器接收到消息:$message");
    });
    // 將子isolate中的發(fā)送器發(fā)送給主isolate麻蹋,那樣就完成了雙向連接
    var childSendPort = childReceivePort.sendPort;
    mainSendPort.send(childSendPort);
  }, mainReceivePort.sendPort); // 將消息接收器中的發(fā)送器發(fā)送給子isolate
}

ReceivePort不用時(shí)需要關(guān)閉,否則程序不會(huì)結(jié)束焊切。

receivePort.close();

2 任務(wù)隊(duì)列

在dart中是由任務(wù)驅(qū)動(dòng)的扮授。
同android中handler類似,在dart運(yùn)行環(huán)境中也是靠事件驅(qū)動(dòng)的专肪,通過event loop不停的從隊(duì)列中獲取消息或者事件來驅(qū)動(dòng)整個(gè)應(yīng)用的運(yùn)行刹勃,isolate發(fā)過來的消息就是通過loop處理。
但是不同的是在android中每個(gè)線程只有一個(gè)looper所對(duì)應(yīng)的messageQueue嚎尤,
而dart中有兩個(gè)隊(duì)列荔仁,一個(gè)叫做event queue(事件隊(duì)列),另一個(gè)叫做microtask queue(微任務(wù)隊(duì)列)
looper會(huì)先從微任務(wù)隊(duì)列中獲取任務(wù)芽死,直到獲取完乏梁,才去執(zhí)行事件隊(duì)列中的任務(wù)。
每當(dāng)執(zhí)行完事件隊(duì)列中的一個(gè)任務(wù)時(shí)关贵,又會(huì)去檢查微任務(wù)隊(duì)列中是否有了任務(wù)遇骑,如果有,先去執(zhí)行微任務(wù)揖曾。

微任務(wù)的執(zhí)行優(yōu)先級(jí)比事件隊(duì)列任務(wù)高

void main() {
  // 在dart中是由任務(wù)驅(qū)動(dòng)的落萎。
  // 同android中handler類似亥啦,在dart運(yùn)行環(huán)境中也是靠事件驅(qū)動(dòng)的,通過event loop不停的從隊(duì)列中獲取消息或者事件來驅(qū)動(dòng)整個(gè)應(yīng)用的運(yùn)行练链,isolate發(fā)過來的消息就是通過loop處理翔脱。
  // 但是不同的是在android中每個(gè)線程只有一個(gè)looper所對(duì)應(yīng)的messageQueue,
  // 而dart中有兩個(gè)隊(duì)列兑宇,一個(gè)叫做event queue(事件隊(duì)列)碍侦,另一個(gè)叫做microtask queue(微任務(wù)隊(duì)列)
  // looper會(huì)先從微任務(wù)隊(duì)列中獲取任務(wù),直到獲取完隶糕,才去執(zhí)行事件隊(duì)列中的任務(wù)。
  // 每當(dāng)執(zhí)行完事件隊(duì)列中的一個(gè)任務(wù)時(shí)站玄,又會(huì)去檢查微任務(wù)隊(duì)列中是否有了任務(wù)枚驻,如果有,先去執(zhí)行微任務(wù)株旷。微任務(wù)的執(zhí)行優(yōu)先級(jí)比事件隊(duì)列任務(wù)高
  print("開始執(zhí)行main方法--- ${DateTime.now()}");

  var receivePort = ReceivePort();
  receivePort.listen((message) {
    print("執(zhí)行$message ${DateTime.now()}");
  });

  receivePort.sendPort.send("事件任務(wù)1");
  Future.microtask(() {
    // 執(zhí)行微任務(wù)
    print("執(zhí)行微任務(wù)1 ${DateTime.now()}");

    // 這邊休眠2s再登,會(huì)阻塞。又佐證了在dart中是單線程的晾剖。
    sleep(const Duration(seconds: 2));
  });
  receivePort.sendPort.send("事件任務(wù)2");
  Future.microtask(() {
    // 執(zhí)行微任務(wù)
    print("執(zhí)行微任務(wù)2 ${DateTime.now()}");
  });
  receivePort.sendPort.send("事件任務(wù)3");

  // 如果在這里休眠3s锉矢,那上面的任務(wù)隊(duì)列中的任務(wù)也都要在3s后才執(zhí)行
  // 只有當(dāng)main任務(wù)執(zhí)行完成之后,才會(huì)執(zhí)行任務(wù)隊(duì)列中的任務(wù)
  sleep(const Duration(seconds: 3));

  print("main方法執(zhí)行結(jié)束齿尽。沽损。。 ${DateTime.now()}");
}

上面的執(zhí)行打印結(jié)果:

開始執(zhí)行main方法--- 2022-03-23 16:35:37.269329
main方法執(zhí)行結(jié)束循头。绵估。。 2022-03-23 16:35:40.287288 ----------------3s后才開始執(zhí)行任務(wù)隊(duì)列中的任務(wù)
執(zhí)行微任務(wù)1 2022-03-23 16:35:40.288894 -------------------------------執(zhí)行微任務(wù)1卡骂,2s后才開始繼續(xù)執(zhí)行任務(wù)
執(zhí)行微任務(wù)2 2022-03-23 16:35:42.291514
執(zhí)行事件任務(wù)1 2022-03-23 16:35:42.294005
執(zhí)行事件任務(wù)2 2022-03-23 16:35:42.294192
執(zhí)行事件任務(wù)3 2022-03-23 16:35:42.294231

3 Future

future意為未來国裳,就是將來要執(zhí)行的事情。需要main方法執(zhí)行結(jié)束后才能執(zhí)行全跨,所以不會(huì)造成阻塞缝左。

void main() {
  // 延遲處理的消息。至少3s后才能執(zhí)行浓若。
  Future.delayed(Duration(seconds: 1), () {
    // 讀取路徑下文件的內(nèi)容渺杉,返回的結(jié)果類型Future<String>
    Future<String> readAsString =
        File(r"/Users/phoenix/Downloads/test1").readAsString();

    // 通過then來獲取到泛型的值。then也有返回值FutureOr<R>七嫌,F(xiàn)utureOr是可以返回void少办,也可以返回Future<R>
    // 這個(gè)是模擬返回一個(gè)int值。int值可以繼續(xù)通過then來獲取诵原。
    Future<int> then = readAsString.then((value) {
      print("value=$value");
      return 100;
    });

    // 其實(shí)我們可以寫成鏈?zhǔn)秸{(diào)用
    File(r"/Users/phoenix/Downloads/test1").readAsString().then((value) {
      print("鏈?zhǔn)秸{(diào)用 value=$value");
    });
  });
}

4 Stream

Stream(流)在dart中也經(jīng)常出現(xiàn)英妓,表示發(fā)出的一些列的異步數(shù)據(jù)挽放。

Future表示稍后獲得的一個(gè)數(shù)據(jù),所有的異步的操作的返回值都用future來表示蔓纠。但是Future只能表示一次異步獲得的數(shù)據(jù)辑畦。而Stream表示多次異步獲得的數(shù)據(jù)。

比如IO處理的時(shí)候腿倚,每次只會(huì)讀取一部分的數(shù)據(jù)和一次性讀取整個(gè)文件相比纯出,Stream的好處是處理過程中內(nèi)存占用較小。而File.readString()敷燎,是一次性讀取整個(gè)文件的內(nèi)容暂筝,文件很大的話會(huì)導(dǎo)致內(nèi)存占用過大的問題

import 'dart:io';

void main() {
  // 通過流的方式來讀寫
  File file = File(r"/Users/phoenix/Downloads/text.epub");
  
  // openRead([int? start, int? end]) 可以傳入讀取的坐標(biāo),也可以不傳
  // 返回的結(jié)果就是對(duì)應(yīng)流的集合
  Stream<List<int>> openRead = file.openRead();
  
  // 設(shè)置讀的監(jiān)聽
  var listen = openRead.listen((event) {
    // 一次讀取65536硬贯。讀取大文件時(shí)焕襟,有可能會(huì)讀取多次
    print("listen(),會(huì)調(diào)用多次 ${event.length}");
  });
  
  // onData 可以替換掉listen方法
  listen.onData((data) {
    print("onData(),替換掉listen(),會(huì)調(diào)用多次");
  });

  // 文件讀完畢之后的回調(diào)
  listen.onDone(() {
    print("文件都讀完了");
  });

  // 也可以控制流暫停和重啟
  // listen.pause();
  // listen.resume();
}

通過stream進(jìn)行讀寫操作

void main() {
  // 通過流的方式來讀寫
  File file = File(r"/Users/phoenix/Downloads/text.epub");
  File outFile = File(r"/Users/phoenix/Downloads/text2.epub");

  // openRead([int? start, int? end]) 可以傳入讀取的坐標(biāo),也可以不傳
  // 返回的結(jié)果就是對(duì)應(yīng)流的集合
  Stream<List<int>> openRead = file.openRead();
  var openWrite = outFile.openWrite();
  
  // 方式一:我們可以直接全部寫入
  openWrite.addStream(openRead);
  
  // 方式二:通過設(shè)置讀的監(jiān)聽饭豹,分段多次寫入
  var listen = openRead.listen((event) {
    openWrite.add(data);
  });
}

stream不能直接設(shè)置多個(gè)監(jiān)聽鸵赖,那怎么設(shè)置多個(gè)監(jiān)聽呢?

方式1:通過BroadcastStream

void main() {
  var openRead = File(r"/Users/phoenix/Downloads/test1").openRead();

  // 調(diào)用多次會(huì)報(bào)以下異常
  // FileSystemException: An async operation is currently pending, path = '/Users/phoenix/Downloads/test1'
  // openRead.listen((event) {});
  // openRead.listen((event) {});

  // 那怎么設(shè)置多個(gè)監(jiān)聽呢拄衰?通過廣播模式
  var asBroadcastStream = openRead.asBroadcastStream();
  asBroadcastStream.listen((event) {
    print("listen1 event=$event");
  });
  asBroadcastStream.listen((event) {
    print("listen2 event=$event");
  });
}

方式1:通過StreamController

void main() {
  var streamController = StreamController.broadcast();
  
  // 在發(fā)送消息之前設(shè)置監(jiān)聽它褪,可以接收到廣播
  streamController.stream.listen((event) {
    print("1....接收 $event");
  });
  
  // 發(fā)送消息
  streamController.add("消息1");
  
  // 在發(fā)送消息之后才去設(shè)置監(jiān)聽,不可以接收到廣播
  streamController.stream.listen((event) {
    print("2....接收 $event");
  });
}

5 async-await

使用async和await的代碼都是異步的翘悉,但是看起來很像同步代碼茫打。當(dāng)我們需要獲取A的結(jié)果,再執(zhí)行B時(shí)镐确,你需要then()->then()包吝,但是利用async與await能夠很好的解決回調(diào)問題。

  • async

    被async修飾的方法就變成了異步方法源葫。返回值是Future或者void诗越。可以通過then獲取到方法的返回值息堂。

    Future<String> readText() async {
      Future<String> readAsString = File(r"/Users/phoenix/Downloads/test1").readAsString();
      return readAsString;
    }
    

    獲取返回值:

    void main() {
      readText().then((value) {
        print("讀取文本:$value");
      });
    }
    
  • await

    await可以將原本異步的操作變成同步操作嚷狞。

    Future<String> readText() async {
      // 如果不寫await關(guān)鍵字,則readAsString()是一個(gè)異步操作荣堰。在第一個(gè)沒有讀取完之后就開始執(zhí)行第二方法了床未。
      // Future<String> readAsString = File(r"/Users/phoenix/Downloads/test1").readAsString();
      // var readAsString2 = File(r"/Users/phoenix/Downloads/test1").readAsString();
    
      // await 等待future執(zhí)行完成后再執(zhí)行后續(xù)代碼
      // 使用了await關(guān)鍵字,則這段代碼就變成同步振坚,會(huì)阻塞后續(xù)代碼的執(zhí)行
      var readAsString =
          await File(r"/Users/phoenix/Downloads/test1").readAsString();
      var readAsString2 =
          await File(r"/Users/phoenix/Downloads/test1").readAsString();
      return readAsString + readAsString2;
    }
    
    void main() {
      readText().then((value) {
        print("讀取文本:$value");
      });
    }
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末薇搁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子渡八,更是在濱河造成了極大的恐慌啃洋,老刑警劉巖传货,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宏娄,居然都是意外死亡问裕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門孵坚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粮宛,“玉大人,你說我怎么就攤上這事卖宠∥¤荆” “怎么了?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵逗堵,是天一觀的道長(zhǎng)秉氧。 經(jīng)常有香客問我,道長(zhǎng)蜒秤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任亚斋,我火速辦了婚禮作媚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帅刊。我一直安慰自己纸泡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布赖瞒。 她就那樣靜靜地躺著女揭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栏饮。 梳的紋絲不亂的頭發(fā)上吧兔,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音袍嬉,去河邊找鬼境蔼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛伺通,可吹牛的內(nèi)容都是我干的箍土。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼罐监,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吴藻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弓柱,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬榮一對(duì)情侶失蹤沟堡,失蹤者是張志新(化名)和其女友劉穎侧但,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弦叶,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俊犯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伤哺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片燕侠。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖立莉,靈堂內(nèi)的尸體忽然破棺而出绢彤,到底是詐尸還是另有隱情,我是刑警寧澤蜓耻,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布茫舶,位于F島的核電站,受9級(jí)特大地震影響刹淌,放射性物質(zhì)發(fā)生泄漏饶氏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一有勾、第九天 我趴在偏房一處隱蔽的房頂上張望疹启。 院中可真熱鬧,春花似錦蔼卡、人聲如沸喊崖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荤懂。三九已至,卻和暖如春塘砸,著一層夾襖步出監(jiān)牢的瞬間节仿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工谣蠢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粟耻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓眉踱,卻偏偏與公主長(zhǎng)得像挤忙,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谈喳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

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