我有一系列期货,我希望将它们组合成一个单一的未来,让它们按顺序执行。
我查看了futures_ordered
函数。它似乎顺序返回结果,但期货同时执行。
我尝试fold
期货,将它们与and_then
结合起来。但是,对于类型系统来说这很棘手。
let tasks = vec![ok(()), ok(()), ok(())];
let combined_task = tasks.into_iter().fold(
ok(()), // seed
|acc, task| acc.and_then(|_| task), // accumulator
);
这会出现以下错误:
error[E0308]: mismatched types
--> src/main.rs:10:21
|
10 | |acc, task| acc.and_then(|_| task), // accumulator
| ^^^^^^^^^^^^^^^^^^^^^^ expected struct `futures::FutureResult`, found struct `futures::AndThen`
|
= note: expected type `futures::FutureResult<_, _>`
found type `futures::AndThen<futures::FutureResult<_, _>, futures::FutureResult<(), _>, [closure@src/main.rs:10:34: 10:42 task:_]>`
我可能接近这个错误,但我已经没想到了。
答案 0 :(得分:4)
Stream
有一个函数buffered
,允许您限制同时轮询的期货数量。
如果您有一系列期货,您可以创建一个流并使用buffered
,如下所示:
let tasks = vec![future1, future2];
let stream = ::futures::stream::iter_ok(tasks);
let mut when_result_ready = stream.buffered(1);
when_result_ready
现在将成为Stream
实施,一次只能轮询一个未来,并在每个未来完成后移至下一个。
<强>更新强>
根据评论和分析,显示buffered
会产生很大的开销,因此另一个解决方案是将每个Future
转换为Stream
和flatten
:
iter_ok(tasks).map(|f|f.into_stream()).flatten()
flatten
指出“每个单独的流都会在进入下一个流程之前耗尽。”意味着在前一个完成之前不会轮询Future
。在我的本地分析中,这似乎比buffered
方法快了约80%。
上述两个答案都会产生Stream
个结果,其中每个来源Future
都会被顺序轮询并返回结果。提问者实际要求的是最后只有一个Future
而不是每个来源Future
的结果,如果是这样的话,Stefan的答案可能会更有用并证明更好性能
答案 1 :(得分:4)
use futures::Stream;
use futures::future::ok;
use futures::stream::iter_ok;
let tasks = vec![ok(()), ok(()), ok(())];
let combined_task = iter_ok::<_, ()>(tasks).for_each(|f| f);
iter_ok
生成传递项的流,并且从不抛出错误(这就是为什么有时需要修复错误类型)。关闭传递给for_each
然后返回Future
以便为每个项目运行 - 这里只是传入的项目。
for_each
然后驱动每个返回的未来完成,然后移动到下一个,就像你想要的那样。它也会在遇到第一个错误时中止,并要求内部期货在成功时返回()
。
for_each
本身会返回一个Future
,它将失败(如上所述)或完成后返回()
。
test tests::bench_variant_buffered ... bench: 22,356 ns/iter (+/- 1,816)
test tests::bench_variant_boxed ... bench: 8,575 ns/iter (+/- 1,042)
test tests::bench_variant_for_each ... bench: 4,070 ns/iter (+/- 531)
答案 2 :(得分:2)
作为mentioned in the comments,你的类型太具体了。
你可以设想fold
的实现是这样的:
let (task0, task1, task2) = (ok(()), ok(()), ok(()));
let mut combined_task = ok(()); // seed
combined_task = combined_task.and_then(|_| task0);
combined_task = combined_task.and_then(|_| task1);
combined_task = combined_task.and_then(|_| task2);
变量combined_task
需要使用相同类型的新值进行适当更新。由于我们从ok(())
开始,因此每个步骤需要返回的类型。但是,and_then
的返回类型不同;它是AndThen
。实际上,AndThen
是一个包含闭包和底层未来的泛型类型,因此每个步骤都会生成一个可能具有不同大小的不同类型:
FutureResult<()>
AndThen<FutureResult<()>, closure0>
AndThen<AndThen<FutureResult<()>, closure0>, closure1>
AndThen<AndThen<AndThen<FutureResult<()>, closure0>, closure1>, closure2>
相反,您可以通过在每个步骤生成盒装特征对象来创建统一类型:
let (task0, task1, task2) = (ok(()), ok(()), ok(()));
let mut combined_task: Box<Future<Item = (), Error = ()>> = Box::new(ok(())); // seed
combined_task = Box::new(combined_task.and_then(|_| task0));
combined_task = Box::new(combined_task.and_then(|_| task1));
combined_task = Box::new(combined_task.and_then(|_| task2));
Box<Future<Item = (), Error = ()>>
Box<Future<Item = (), Error = ()>>
Box<Future<Item = (), Error = ()>>
Box<Future<Item = (), Error = ()>>
转换回fold
语法:
let combined_task: Box<Future<Item = (), Error = ()>> =
tasks.into_iter().fold(Box::new(ok(())), |acc, task| {
Box::new(acc.and_then(|_| task))
});
另见:
答案 3 :(得分:1)
当我需要这样的内容时(主要是因为我正在调试问题),我最终写了一个news
组成seq
like so的组合器:
loop_fn
答案 4 :(得分:1)
就我而言(稳定的async/await
),此代码非常有帮助:
use futures::{stream, StreamExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
let data = vec![1,2,3];
stream::iter(data).for_each(|id| async move {
let request = async { id }; // async io request
let res = request.await;
println!("res: {:?}", res);
()
}).await;
Ok(())
}