10.5 async/await 入门:Future 与异步运行时

Rust 的并发模型不仅支持基于操作系统线程的并行执行,还提供了高效的异步编程能力,适用于 I/O 密集型任务(如网络请求、文件读写)。通过 async/await 语法和 Future trait,Rust 能在单线程中并发处理大量 I/O 操作,避免线程阻塞带来的资源浪费。

Future 与异步函数

在 Rust 中,异步函数返回一个 Future,它代表一个尚未完成的计算。调用异步函数不会立即执行,而是返回一个可被调度执行的 Future 对象:

async fn fetch_data() -> String {
    // 模拟网络延迟
    "data".to_string()
}

要真正执行这个函数,必须在一个异步运行时(async runtime)中 .await 它:

#[tokio::main]
async fn main() {
    let result = fetch_data().await;
    println!("{}", result);
}

await 会挂起当前异步任务,直到 Future 完成,期间运行时可调度其他任务执行,从而实现并发。

异步运行时:tokio 与 async-std

Rust 标准库不包含异步运行时,需依赖外部 crate。目前主流选择有:

  • tokio:功能全面,性能优异,广泛用于生产环境;
  • async-std:API 设计更贴近标准库,学习曲线平缓。

tokio 为例,添加依赖:

[dependencies]
tokio = { version = "1", features = ["full"] }

然后使用 #[tokio::main] 属性将 main 函数标记为异步入口:

#[tokio::main]
async fn main() {
    // 异步代码
}

并发执行多个异步任务

使用 tokio::spawn 可并发启动多个异步任务:

use tokio;

#[tokio::main]
async fn main() {
    let task1 = tokio::spawn(async {
        println!("Task 1 start");
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
        println!("Task 1 done");
    });

    let task2 = tokio::spawn(async {
        println!("Task 2 start");
        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
        println!("Task 2 done");
    });

    task1.await.unwrap();
    task2.await.unwrap();
}

输出顺序可能交错,表明任务并发执行。

异步与同步代码的界限

  • 异步函数只能在异步上下文中调用(即另一个 async 函数内或运行时中);
  • 阻塞操作(如 thread::sleep)不应在异步任务中使用,应改用异步等价物(如 tokio::time::sleep),否则会阻塞整个运行时线程;
  • async 不等于“多线程”——默认情况下,tokio 使用单线程调度器(可通过配置启用多线程)。

与传统线程模型的对比

特性 线程(std::thread 异步(async/await
资源开销 每线程 MB 级栈空间 每任务 KB 级堆内存
上下文切换 操作系统调度,成本高 运行时协作式调度,成本低
适用场景 CPU 密集型、并行计算 I/O 密集型、高并发连接
编程模型 显式线程管理 类似同步代码的异步逻辑

小结

async/await 是 Rust 处理高并发 I/O 的核心机制。它通过 Future 和运行时调度,在保持代码可读性的同时实现高效资源利用。虽然需要引入外部运行时(如 tokio),但其生态成熟、性能卓越。掌握异步编程,是构建现代网络服务、Web 后端和高性能客户端应用的关键技能。后续深入可探索流(Stream)、异步通道(tokio::sync::mpsc)和异步锁等高级特性。

#Rust 入门教程 分享于 1 周前

内容由 AI 创作和分享,仅供参考