代码之家  ›  专栏  ›  技术社区  ›  Kyle Carow

什么时候使用FuturesOrdered?

  •  0
  • Kyle Carow  · 技术社区  · 2 年前

    对于同一个函数,我有两种方法,我正在尝试看哪一种更惯用或更具表演性。

    query_many_points() 函数是接收两个相同大小的数组,并通过 query_point() 函数,返回的向量 Response s-包含从API反序列化的数据的自定义结构。API响应的顺序必须与输入的顺序完全匹配。我相信这两种方法都能做到这一点。

    我最初是通过在Vec中收集期货来写这篇文章的,然后迭代到 await 并按顺序展开结果。

    方法1:把手的V形

    fn query_many_points(xs: &[f64], ys: &[f64]) -> anyhow::Result<Vec<Response>> {
        let agent = ureq::agent();
        let semaphore = Arc::new(Semaphore::new(TASKS_LIMIT));
    
        // Concurrently query all given coordinates, limited to TASKS_LIMIT concurrent tasks
        // Returned Vec<Response> preserves order of input coordinates
        let runtime = Runtime::new()?;
        runtime.block_on(async {
            // Create a vector of task handles, preserving order with input coordinates
            let handles = xs
                .iter()
                .zip(ys.iter())
                .map(|(&x, &y)| {
                    // Obtain permit from semaphore when available
                    let permit = semaphore.clone().acquire_owned();
                    let agent = agent.clone();
                    // Spawn new task and query point, returning handle containing Result<Response>
                    tokio::spawn(async move {
                        let result = query_point(x, y, &agent);
                        drop(permit);
                        result
                    })
                })
                .collect::<Vec<_>>(); // this line is where the approaches start to differ
    
            // Await all tasks to complete in order and collect into Vec<Response>
            let mut responses = Vec::with_capacity(handles.len());
            for handle in handles {
                responses.push(handle.await??);
            }
    
            Ok(responses)
        })
    }
    

    然后我发现 futures::stream::FuturesOrdered 然后转了一圈,想出了第二个解决方案。

    方法2:未来有序

    fn query_many_points(xs: &[f64], ys: &[f64]) -> anyhow::Result<Vec<Response>> {
        let agent = ureq::agent();
        let semaphore = Arc::new(Semaphore::new(TASKS_LIMIT));
    
        // Concurrently query all given points, limited to TASKS_LIMIT concurrent tasks
        // Returned Vec<Response> preserves order of input points
        let runtime = Runtime::new()?;
        runtime.block_on(async {
            // Create a FuturesOrdered of task handles
            let handles = xs
                .iter()
                .zip(ys.iter())
                .map(|(&x, &y)| {
                    // Obtain permit from semaphore when available
                    let permit = semaphore.clone().acquire_owned();
                    let agent = agent.clone();
                    // Spawn new task and query point, returning handle containing Result<Response>
                    tokio::spawn(async move {
                        let result = query_point(x, y, &agent);
                        drop(permit);
                        result
                    })
                })
                .collect::<FuturesOrdered<_>>(); // this line is where the approaches start to differ
    
            // Await completion of all tasks in order and collect into Vec<Response>
            handles.try_collect::<Vec<_>>().await?.into_iter().collect()
        })
    }
    

    哪种方法最好?还有其他可以改进的地方吗?两者保持秩序完全相同,对吧?

    1 回复  |  直到 2 年前
        1
  •  3
  •   Colonel Thirty Two    2 年前

    考虑一下你不打电话的情况 tokio::spawn 并从中回归未来 map

    如果您通过 Vec 期货和看涨期权 await 对于每一个未来,一次只有一个未来开始,下一个未来只有在上一个未来结束时才会开始。

    如果您使用 FuturesOrdered ,它将一次启动多个future,这可能会允许更多的并发性,因为一个启动的future可以取得进展,而另一个必须等待。


    在你的具体例子中,这并不重要——你为每个未来生成一个任务,tokio将开始自己执行它们(可能在另一个线程上),而你加入的未来除了“等待任务完成”之外不会做任何工作。你也可以直接使用vec。