Dart中的異步編程(二):Stream

Stream 顧名思義就是流,簡單理解,其實就是一個異步數據隊列而已。我們知道隊列的特點是先進先出的,Stream也正是如此。
Stream 分爲兩種,單訂閱流(single subscription) 和 廣播流(broadcast)。

單訂閱流

單訂閱流的特點是隻允許存在一個監聽器,即使該監聽器被取消後,也不允許再次註冊監聽器。

如何創建Stream

Stream.periodic

periodic可以創建一個 按一定間隔,重複發出事件的流。

import 'dart:async';
void main() {
  // test();
  testPeriodic();
}
testPeriodic() async {
  // 使用 periodic 創建流,第一個參數爲間隔時間,第二個參數爲回調函數,這個回調函數參數是整型(0開始遞增)
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  // await for循環從流中讀取
  await for (var i in stream) {
    print(i);
  }
}

int callback(int computationCount) {
  // print("value $value");
  return computationCount;
}

打印結果
0
1
2
3
4

每一秒生成一次自然數列,callback函數用於對生成的整數(computationCount,從0開始遞增)進行處理,處理後再放入Stream中。這裏並未處理,直接返回了。要注意,這個流是無限的。當然也可以約束條件使之停止。在後面會介紹如何給流設置條件。

約束條件

take 和 takeWhile

Stream take(int count) 用於 約束Stream中的元素數量,當流發出 count 個數據事件時,事件將會結束,stream is done。

testPeriodic() async {
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  stream = stream.take(5);// 當放入5個元素後,監聽會停止,Stream會關閉
  // await for循環從流中讀取
  await for (var i in stream) {
    print(i);
  }
}

打印結果:
0
1
2
3
4

Stream takeWhile(bool test(T element)) 與 take 類似,它是參數是一個返回值爲bool的函數,函數功能是對當前元素進行判斷,不滿足條件則取消監聽。

  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  stream = stream.takeWhile((i) {
    return i <= 5;
}

skip 和 skipWhile

表示從Stream中跳過前count個元素

testPeriodic() async {
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  stream = stream.takeWhile((i) {
    return i <= 5;
  });
  stream = stream.skip(3);//表示從Stream中跳過前兩個元素
  // await for循環從流中讀取
  await for (var i in stream) {
    print(i);
  }
}

skipWhile(bool test(T element)) 方法與takeWhile用法是相同的,傳入一個函數對結果進行判斷,表示跳過滿足條件的。

有幾個注意事項

  • 該方法只是從Stream中獲取元素時跳過,被跳過的元素依然是被執行了的,所耗費的時間依然存在,其實只是跳過了執行完的結果而已。
  • 如果該流發出的數據事件小於count,完成之前 stream將不會發射任何事件。

Stream.fromFuture

 Stream.fromFuture(Future.delayed(Duration(seconds: 2), () {
    return "Hello";
  })).listen((onData) {
    print(onData);
  }, onError: (e) {
    print("onError $e");
  }, onDone: () {
    print("onDone");
  }, cancelOnError: false);//cancelOnError 爲true時,出現onError時,onDone將不會回調

打印結果
Hello
onDone

該方法從一個Future創建Stream,當Future執行完成時,就會放入Stream中,而後從Stream中將任務完成的結果取出。
監聽Stream的方法:
監聽一個流最常見的方法就是listen。當有事件發出時,流將會通知listener。Listen方法提供了這幾種觸發事件:

  • onData(必填):收到數據時觸發
  • onError:收到Error時觸發
  • onDone:結束時觸發
  • cancelOnError:遇到第一個Error時是否取消訂閱,默認爲false。cancelOnError 爲true時,出現onError時,onDone將不會回調
    順便提一下:
    獲取Stream結果

Stream.fromFutures

可以接收多個異步操作的結果(成功或失敗)。 也就是說,在執行異步任務時,可以通過多次觸發成功或失敗事件來傳遞結果數據或錯誤異常。 Stream 常用於會多次讀取數據的異步任務場景,如網絡內容下載、文件讀寫等.

  Stream.fromFutures([
    // 1秒後返回結果
    Future.delayed(new Duration(seconds: 1), () {
      return "hello";
    }),
    // 拋出一個異常
    Future.delayed(new Duration(seconds: 2), () {
      throw AssertionError("Error");
    }),
    // 3秒後返回結果
    Future.delayed(new Duration(seconds: 3), () {
      return 520;
    })
  ]).listen((data) {
    print(data);
  }, onError: (e) {
    print(e.message);
  }, onDone: () {
    print("onDone");
  });

打印結果
hello
Error
520
onDone

Stream.fromIterable

該方法從一個集合創建Stream

  List<int> list = [1, 2, 3];
  Stream.fromIterable(list).listen((data) {
    print(data);
  }, onError: (e) {
    print(e.message);
  }, onDone: () {
    print("onDone");
  },cancelOnError: false);

打印結果
1
2
3
onDone

Stream.value

Dart2.5 新增的方法,用於從單個值創建Stream。

  Stream stream = Stream.value("Hello");
  // await for循環從流中讀取
 stream.forEach((result){
   print(result);
 });

StreamController

StreamController 工具類 就如同一個管道,在這個管道中封裝了一個 Stream ,並向我們提供了兩個接口來操作 Stream 。分別是:

  • sink 從Stream中的一端插入數據
  • stream 從Stream的另一外彈出數據
//任意類型的流,也可定義泛型
  StreamController controller = StreamController();

// 監聽這個流的出口,當有data流出時,打印這個data
  controller.stream.listen((data) => print(data),
      onError: (e) => print(e),
      onDone: () => print("onDone"),
      cancelOnError: false);

  controller.add(123);
  controller.add("hello");
  controller.sink.add('world');
  controller.addError("this is error");
  controller.add(false);
  controller.close();// 調用close方法,結束Stream中的邏輯處理,釋放資源,如果不調用close方法,將不會觸發onDone回調。

打印結果
123
hello
world
this is error
false
onDone

監聽Stream隊列
細心的你可能看到,上述代碼用過多種方式監聽Stream,用過四種方式,await for循環,這也是官方推薦的方式,看起來更簡潔友好,是使用forEach方法或listen方法。最後一種方式是 將Stream中所有數據存儲在List中,然後遍歷list,代碼如下:

testList() async {
  Stream stream = Stream.fromIterable([1, 2, 3]);
  List list = await stream.toList();
  for (var i in list) {
    print(i);
  }
}

廣播流

在普通的單訂閱流中調用兩次listen會報錯

  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (computationCount)=>computationCount);
  stream.listen(print);
  stream.listen(print);

將會報異常

Unhandled exception:
Bad state: Stream has already been listened to.

廣播流則可以允許多個監聽器存在,就如同廣播一樣,凡是監聽了廣播流,每個監聽器都能獲取到數據。
要注意,如果在觸發事件時將監聽者正添加到廣播流,當前正在觸發的事件和之前的時間,監聽着都不會收到。如果取消監聽,監聽者會立即停止接收事件。

創建廣播流方式

一種直接從Stream創建,另一種使用StreamController創建

從Stream創建

  // 調用 Stream 的 asBroadcastStream 方法創建
  Stream<int> stream = Stream<int>.fromIterable([1, 2, 3])
  .asBroadcastStream();

  stream.listen((i)=>print("listener1:$i"));
  stream.listen((i)=>print("listener2:$i"));

打印結果
listener1:1
listener2:1
listener1:2
listener2:2
listener1:3
listener2:3

從StreamController創建

  // 創建廣播流
  StreamController streamController = StreamController.broadcast();
  Stream stream = streamController.stream;

  //設置監聽
  stream.listen((i) => print("listener1:$i"));
  stream.listen((i) => print("listener2:$i"));

  //添加事件
  streamController.add("event1");
  streamController.add("event2");

打印結果
listener1:event1
listener2:event1
listener1:event2
listener2:event2

數據裝換

StreamTransformer

stream通過 數據轉換方法 transform,傳入參數StreamTransformer(可以把StreamTransformer當做一個數據轉換的策略),返回一個新的Stream。
通俗的講:就是把一個 Stream 作爲輸入,然後經過計算或數據轉換,輸出爲另一個 Stream。另一個 Stream 中的數據類型可以不同於原類型,數據多少也可以不同。

我們寫一個例子,將隊列裏的小寫字母轉換爲大寫字母

  StreamController<String> controller = StreamController<String>();
  
  // 創建 StreamTransformer對象
  StreamTransformer streamTransformer = StreamTransformer<String, String>.fromHandlers(
    handleData: (String data, EventSink sink) {
      // 操作數據後,轉換爲 double 類型
      sink.add(data.toUpperCase());
    }, 
    handleError: (error, stacktrace, sink) {
      sink.addError('wrong: $error');
    }, 
    handleDone: (sink) {
      sink.close();
    },
  );
  
  // 調用流的transform方法,傳入轉換對象
  Stream<String> stream = controller.stream.transform(streamTransformer);

  stream.listen(print,onDone: ()=>print("onDone"));

  // 添加數據,這裏的類型是int
  controller.add('a');
  controller.add('b'); 
  controller.add('c'); 
  
  // 調用後,觸發handleDone回調
  controller.close();

輸出結果
A
B
C
onDone

總結 Stream 和 Future

Stream 和 Future 是 Dart 異步處理的核心 API。
Future 表示稍後獲得的一個數據,所有異步的操作的返回值都用 Future 來表示。但是 Future 只能表示一次異步獲得的數據。而 Stream 表示多次異步獲得的數據。比如界面上的按鈕可能會被用戶點擊多次,所以按鈕上的點擊事件(onClick)就是一個 Stream 。
簡單地說,Future將返回一個值,而Stream將返回多次值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章