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将返回多次值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值