天天看點

Dart異步程式設計

我們知道Dart是基于單線程模型的語言。我們在開發進行耗時操作時,比如網絡請求會阻塞我們的程式,在Dart中也有并發機制,叫做isolate。APP的啟動入口

main

函數就是一個類似Android主線程的一個主isolate。和Java的Thread不同的是,Dart中的isolate無法共享記憶體。

  • isolate機制
import 'dart:io';
import 'dart:isolate';

var m;
void main() {
  //建立消息接收器
  m = 89;//主isolate定義m的值
  var receivePort = new ReceivePort();
  Isolate.spawn(entryPoint, receivePort.sendPort); //相當于java中開啟線程操作
  receivePort.listen((t) {
    if (t is SendPort) {
      t.send("消息2"); // 通過接受子Isolate發出的sendPort來發送消息大主Isolate
    } else {
      print(t);
    }
  });
  print(message);
}

var message = "hello";

void entryPoint(SendPort sendPort) {
  // isolate是記憶體隔離的,mi的值是在主isolate定義的是以這裡獲得null
  print(m);
  sendPort.send("消息1"); //發消息到主Isolate
  sleep(Duration(seconds: 10)); //延時10秒列印 消息2
  //建立消息接收器
  var receivePort2 = new ReceivePort();
  receivePort2.listen((t) {
    print(t);
  });
  sendPort.send(receivePort2.sendPort); //發送發送器到主Isolate,實作通信
}
           
  • 任務隊列

​ 同Android Handler類似,在Dart運作環境中也是靠事件驅動的,通過event loop不停的從隊列中擷取消息或者事件來驅動整個應用的運作,isolate發過來的消息就是通過loop處理。但是不同的是在Android中每個線程隻有一個Looper所對應的MessageQueue,而Dart中有兩個隊列,一個叫做event queue(事件隊列),另一個叫做microtask queue(微任務隊列)。

​ Dart在執行完main函數後,就會由Loop開始執行兩個任務隊列中的Event。首先Loop檢查微服務隊列,依次執行Event,當微服務隊列執行完後,就檢查Event queue隊列依次執行,在執行Event queue的過程中,每執行完一個Event就再檢查一次微服務隊列。是以微服務隊列優先級高,可以利用微服務進行插隊。

import 'dart:isolate';

void main() {
  var receivePort = new ReceivePort();
  receivePort.sendPort.send("消息");

  receivePort.listen((t) {
    print(t);
  });

  Future.microtask(() { //優先上面執行
    print("微任務隊列");
  });
}
           
  • Future

    在 Dart 庫中随處可見 Future 對象,通常異步函數傳回的對象就是一個 Future。 當一個 future 執行完後,他裡面的值 就可以使用了,可以使用

    then()

    來在 future 完成的時候執行其他代碼。Future對象其實就代表了在事件隊列中的一個事件的結果。

    then()

    的傳回值同樣是一個future對象,可以利用隊列的原理進行組合異步任務
List s = ["yuuu", "rtrt", "ewe", "wew"];
  Future.forEach(s, (t) {
    print(t);
  });

  new File(r"E:\common\記錄(舊).txt").readAsString().then((String s) {
    print(s);
  });
           

上面的方式是等待執行完成讀取檔案之後,再執行一個新的future。如果我們需要等待一組任務都執行完成再統一處理一些事情,可以通過

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的結果
     print(values[1]);//第二個future的結果
  });
           
  • Steam

    Stream(流) 在 Dart API 中也經常出現,表示發出的一系列的異步資料。 Stream 是一個異步資料源,它是 Dart 中處理異步事件流的統一 API。

    ​ Future 表示稍後獲得的一個資料,所有異步的操作的傳回值都用 Future 來表示。但是 Future 隻能表示一次異步獲得的資料。而 Stream 表示多次異步獲得的資料。比如 IO 處理的時候,每次隻會讀取一部分資料和一次性讀取整個檔案的内容相比,Stream 的好處是處理過程中記憶體占用較小。而 File 的

    readAsString()

    是一次性讀取整個檔案的内容進來,雖然獲得完整内容處理起來比較友善,但是如果檔案很大的話就會導緻記憶體占用過大的問題。
Stream<List<int>> steam = new File(
      r"E:\common\記錄(舊).txt").openRead();//讀取流檔案
  var file = new File(r"E:\common\apk\20180823\test.txt");
  var openWrite = file.openWrite();
  openWrite.addStream(steam);//寫入流檔案
  var listen = steam.listen((List<int> bytes) {
    print(12131); //執行多次,在檔案較大時候每次讀取64k的大小
  });

  listen.onData((_){
    print("替代listene");
  });
  listen.onDone((){
    print("結束");
  });
  listen.onError((e,s){
    print("異常");
  });
  //暫停,如果沒有繼續則會退出程式
  listen.pause();
  //繼續
  listen.resume();
           
  • 廣播模式

    Stream有兩種訂閱模式:單訂閱和多訂閱。單訂閱就是隻能有一個訂閱者,上面的使用我們都是單訂閱模式,而廣播是可以有多個訂閱者。通過 Stream.asBroadcastStream() 可以将一個單訂閱模式的 Stream 轉換成一個多訂閱模式的 Stream,isBroadcast 屬性可以判斷目前 Stream 所處的模式。

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

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

需要注意的是,多訂閱模式如果沒有及時添加訂閱者則可能丢資料。

//預設是單訂閱
  var stream = Stream.fromIterable([1, 2, 3]);
  //3s後添加訂閱者 不會丢失資料
  new Timer(new Duration(seconds: 3), () => stream.listen(print));

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


  //這裡沒有丢失,因為stream通過asBroadcastStream轉為了多訂閱,但是本質是單訂閱流,并不改變原始 stream 的實作特性
  var broadcastStream = Stream.fromIterable([1, 2, 3]).asBroadcastStream();
  new Timer(new Duration(seconds: 3), () => broadcastStream.listen(print));
           
  • async/await

    ​ 使用

    async

    await

    的代碼是異步的,但是看起來很像同步代碼。當我們需要獲得A的結果,再執行B,時,你需要

    then()->then()

    ,但是利用

    async

    await

    能夠非常好的解決回調地獄的問題:
//async 表示這是一個異步方法,await必須再async方法中使用
//異步方法隻能傳回 void和Future
Future<String> readFile() async {
  //await 等待future執行完成再執行後續代碼
  String s= await new File(  r"E:\common\記錄(舊).txt").readAsString();
  String s2 = await new File(  r"E:\common\記錄(舊).txt").readAsString();
  //自動轉換為 future
  return s;
}