Rust:futures::future::join_all(...).await逐个运行future,但需要并行处理

6
我可以进行翻译。以下是需要翻译的内容:

我有一个 async 函数(实现并不重要):

async fn long_task(task_number: i32) {
    // do some long work
    println!("Task {} executed", task_number);
}

我想并发地运行这个函数n次,因此我定义了这个函数:

async fn execute_long_tasks_async(n: i32) {
    let mut futures = Vec::new();
    for i in 1..=n {
        futures.push(long_task(i));
    }
    futures::future::join_all(futures).await;
}

我正在使用 join_all 函数等待所有任务执行完成,然后在我的 main 函数中调用该函数:

fn main() {
    futures::executor::block_on(execute_long_tasks_async(3));
}

我的问题是任务按顺序运行:

Executing task 1
Task 1 executed
Executing task 2
Task 2 executed
Executing task 3
Task 3 executed

但是我本以为它会并发运行,而且我会得到类似这样的结果:

Executing task 1
Executing task 3
Executing task 2
Task 1 executed
Task 3 executed
Task 2 executed

是否有代替 futures::future::join_all 的方法来并行运行所有任务?

我想使用 await 来创建一个简单的示例来演示 asyncawait


1
我有以下异步函数(实现并不重要):- 实现很重要。据我所知,block_on使用单个线程执行。Future运行直到它被阻塞在某些.await上或者直到它到达结尾。如果在您的async fn中没有.await,它将永远不会产生,并且在完成之前不允许其他future执行。 - justinas
1个回答

13

join_all 并发地执行任务(而非并行)。它有一个限制,即只能在任务主动让出时切换到其他任务。此外,如果第一个任务未准备好,它总是优先处理该任务。

如果您的函数被定义为

async fn long_task(task_number: i32) {
    println!("Executing Task {}", task_number);
    tokio::time::delay_for(Duration::from_secs(1)).await;
    println!("Task {} executed", task_number);
}

如果long_task不进行yield操作(例如因为它会阻塞线程),那么在其他任务开始之前,该函数将运行完毕:

然而,如果函数中间的await/suspend点提供给join_all运行其他函数/Futures的机会,您将观察到预期的输出。

async fn long_task(task_number: i32) {
    println!("Executing Task {}", task_number);
    std::thread::sleep(Duration::from_secs(1));
    println!("Task {} executed", task_number);
}
如果你有这样的函数,它们通常不适合于async世界。async函数意在不占用CPU太多资源,不应该阻塞线程 - 这样可以使得安排在同一执行程序上的其他函数仍能运行。
一个可能提供更多并行性的替代方案是使用一个多线程异步运行时(例如tokio),并将每个async函数作为单独的任务spawn出来。在这种情况下,任务可以在不同的CPU核心和线程上运行,并且彼此之间的阻塞就不会那么严重。然后,您可以对返回的JoinHandle集合使用join_all来等待所有任务完成。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接