按顺序执行期货集合

时间:2018-01-03 16:53:55

标签: rust future

我有一系列期货,我希望将它们组合成一个单一的未来,让它们按顺序执行。

我查看了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
);

playground

这会出现以下错误:

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:_]>`

我可能接近这个错误,但我已经没想到了。

5 个答案:

答案 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转换为Streamflatten

iter_ok(tasks).map(|f|f.into_stream()).flatten()

flatten指出“每个单独的流都会在进入下一个流程之前耗尽。”意味着在前一个完成之前不会轮询Future。在我的本地分析中,这似乎比buffered方法快了约80%。

上述两个答案都会产生Stream个结果,其中每个来源Future都会被顺序轮询并返回结果。提问者实际要求的是最后只有一个Future而不是每个来源Future的结果,如果是这样的话,Stefan的答案可能会更有用并证明更好性能

答案 1 :(得分:4)

结合iter_okStream::for_each

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是一个包含闭包和底层未来的泛型类型,因此每个步骤都会生成一个可能具有不同大小的不同类型:

  1. FutureResult<()>
  2. AndThen<FutureResult<()>, closure0>
  3. AndThen<AndThen<FutureResult<()>, closure0>, closure1>
  4. AndThen<AndThen<AndThen<FutureResult<()>, closure0>, closure1>, closure2>
  5. 相反,您可以通过在每个步骤生成盒装特征对象来创建统一类型:

    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)); 
    
    1. Box<Future<Item = (), Error = ()>>
    2. Box<Future<Item = (), Error = ()>>
    3. Box<Future<Item = (), Error = ()>>
    4. Box<Future<Item = (), Error = ()>>
    5. 转换回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(())
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ad5feaf0cbb3597730c22df2eaf4a606