Stream:按顺序到达的 Future
回想一下,我们在本章前面的“消息传递”一节中使用过异步信道的接收端。异步的
recv 方法会随着时间推移产生一系列值。这是一种更通用的模式的实例,称为 stream(流)。许多概念都可以很自然地
表示为流:队列中逐渐变得可用的项;当完整数据集太大而无法放入
计算机内存时,从文件系统中逐块增量拉取的数据块;或者随时间推移通过网络到达的数据。
因为流也是 future,所以我们可以将它们与任何其他类型的 future 一起使用,
并以有趣的方式组合它们。例如,我们可以将事件批量处理以避免
触发过多的网络调用,为一系列长时间运行的操作设置超时,或者对用户界面事件进行节流以避免做无谓的工作。
早在第 13 章,我们在“Iterator trait 和 next 方法”一节中就见过值的序列,但迭代器与
异步信道接收端之间有两个区别。第一个区别是时间:迭代器是
同步的,而信道接收端是异步的。第二个区别
是 API。直接使用 Iterator 时,我们调用它同步的
next 方法。而对于 trpl::Receiver 这个流,我们调用的
是异步的 recv 方法。除此之外,这些 API 给人的感觉非常相似,
而这种相似并非巧合。流就像是迭代的一种异步形式。
不过,trpl::Receiver 是专门等待接收
消息的,而通用的流 API 要宽泛得多:它像 Iterator 那样提供
下一项,只不过是以异步的方式。
Rust 中迭代器与流之间的这种相似性意味着,我们实际上
可以从任何迭代器创建一个流。与迭代器一样,我们可以通过调用流的
next 方法并 await 其输出来处理流,如示例
17-21 所示——不过这段代码还无法编译。
extern crate trpl; // required for mdbook test
fn main() {
trpl::block_on(async {
let values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let iter = values.iter().map(|n| n * 2);
let mut stream = trpl::stream_from_iter(iter);
while let Some(value) = stream.next().await {
println!("The value was: {value}");
}
});
}
我们从一个数字数组开始,将其转换为迭代器,然后
调用 map 将所有值翻倍。接着,我们使用 trpl::stream_from_iter 函数将迭代器转换成
流。然后,我们用 while let 循环遍历流中
陆续到达的项。
不幸的是,当我们尝试运行这段代码时,它无法编译,而是
报告说没有可用的 next 方法:
error[E0599]: no method named `next` found for struct `tokio_stream::iter::Iter` in the current scope
--> src/main.rs:10:40
|
10 | while let Some(value) = stream.next().await {
| ^^^^
|
= help: items from traits can only be used if the trait is in scope
help: the following traits which provide `next` are implemented but not in scope; perhaps you want to import one of them
|
1 + use crate::trpl::StreamExt;
|
1 + use futures_util::stream::stream::StreamExt;
|
1 + use std::iter::Iterator;
|
1 + use std::str::pattern::Searcher;
|
help: there is a method `try_next` with a similar name
|
10 | while let Some(value) = stream.try_next().await {
| ~~~~~~~~
正如这段输出所解释的,出现这个编译错误的原因是:我们需要让
正确的 trait 进入作用域,才能使用 next 方法。根据我们到目前为止的讨论,
你有理由认为那个 trait 是 Stream,但实际上
它是 StreamExt。Ext 是 extension(扩展)的缩写,是
Rust 社区中用一个 trait 扩展另一个 trait 的常见模式。
Stream trait 定义了一个底层接口,实际上将
Iterator 和 Future 两个 trait 组合在了一起。StreamExt 在 Stream 之上提供了一组更高层的 API,
包括 next 方法以及其他类似于 Iterator trait 所提供的工具
方法。Stream 和
StreamExt 尚未成为 Rust 标准库的一部分,但生态系统中的
大多数 crate 都使用类似的定义。
修复这个编译错误的方法是为
trpl::StreamExt 添加一条 use 语句,如示例 17-22 所示。
extern crate trpl; // required for mdbook test
use trpl::StreamExt;
fn main() {
trpl::block_on(async {
let values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// --snip--
let iter = values.iter().map(|n| n * 2);
let mut stream = trpl::stream_from_iter(iter);
while let Some(value) = stream.next().await {
println!("The value was: {value}");
}
});
}
把所有这些部分组合在一起,这段代码就能按照我们想要的方式工作了!
更重要的是,现在 StreamExt 已经在作用域中,我们可以使用它的所有工具
方法,就像使用迭代器一样。