异步编程 advanced 30 分钟 更新 2026-06-15

并发任务

用 join! 和 spawn 让多个异步任务并发执行。

并发任务

你会学到什么

  • tokio::join! 让多个 Future 并发推进,总耗时约等于最慢的那个。
  • tokio::spawn 把任务交给运行时调度,返回 JoinHandle
  • 并发(concurrency)和并行(parallelism)的区别。

最小示例

let (a, b) = tokio::join!(fetch(1), fetch(2)); // 两个同时进行

运行代码

cd examples
cargo run -p rt_33_async_tasks
cargo test -p rt_33_async_tasks

代码讲解

顺序 .await 会一个等完再等下一个;join! 则同时驱动所有 Future:

async fn run_concurrently() -> Vec<u32> {
    let (first, second, third) = tokio::join!(work(1, 30), work(2, 10), work(3, 20));
    vec![first, second, third]
}

join! 按参数顺序返回结果,和谁先完成无关。

spawn 适合“发射后不管”或需要 JoinHandle 的场景:

let handle = tokio::spawn(work(1, 5));
let value = handle.await.unwrap();

注意 spawn 的任务必须 'staticSend,因为它可能在另一个线程上运行。

常见错误

误以为 join! 会按完成顺序返回:

let (a, b) = tokio::join!(slow(), fast());
// a 仍然是 slow() 的结果,顺序由参数位置决定

练习

  • join! 并发请求 3 个 id,对比顺序 await 的总耗时。
  • spawn 启动 5 个任务,收集所有 JoinHandle 的结果求和。

小结

join! 并发等待一组 Future,spawn 把任务交给调度器。异步并发用单线程也能重叠等待时间。

下一步

接下来进入生态库阶段,学习 serde、anyhow 和 clap。

完整示例代码

下面是 examples/33_async_tasks/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。

examples/33_async_tasks/src/main.rs
//! 并发任务:join! 同时等待,spawn 后台执行。

use std::time::Duration;

async fn work(id: u32, delay_ms: u64) -> u32 {
    tokio::time::sleep(Duration::from_millis(delay_ms)).await;
    id * 10
}

/// join! 让多个 Future 并发推进,而不是顺序等待。
async fn run_concurrently() -> Vec<u32> {
    let (first, second, third) = tokio::join!(work(1, 30), work(2, 10), work(3, 20));
    vec![first, second, third]
}

/// spawn 把任务交给运行时调度,返回 JoinHandle。
async fn spawn_sum() -> u32 {
    let mut handles = Vec::new();
    for id in 1..=3 {
        handles.push(tokio::spawn(work(id, 5)));
    }
    let mut total = 0;
    for handle in handles {
        total += handle.await.expect("任务 panic");
    }
    total
}

#[tokio::main]
async fn main() {
    println!("concurrent = {:?}", run_concurrently().await);
    println!("spawned sum = {}", spawn_sum().await);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn joins_in_order() {
        // join! 按参数顺序返回结果,与完成先后无关。
        assert_eq!(run_concurrently().await, vec![10, 20, 30]);
    }

    #[tokio::test]
    async fn spawns_and_sums() {
        assert_eq!(spawn_sum().await, 60);
    }
}
examples/33_async_tasks/Cargo.toml
[package]
name = "rt_33_async_tasks"
version.workspace = true
edition.workspace = true
publish.workspace = true

[dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }