21-async/await

async/await

译文 · 基于 Asynchronous Programming in Rust

async/await

原文链接: https://rust-lang.github.io/async-book/03_async_await/01_chapter.html

在[第一章]中,我们简要介绍了 async/.await。本章将更详细地讨论 async/.await,解释其工作原理以及 async 代码与传统 Rust 程序的差异。

async/.await 是 Rust 的特殊语法,可在等待操作完成时让出当前线程的控制权而非阻塞,允许其他代码在等待期间推进。

使用 async 有两种主要方式:async fn 和 async 块。每种都返回实现 Future trait 的值:

// `foo()` returns a type that implements `Future<Output = u8>`.
// `foo().await` will result in a value of type `u8`.
async fn foo() -> u8 { 5 }

fn bar() -> impl Future<Output = u8> {
    // This `async` block results in a type that implements
    // `Future<Output = u8>`.
    async {
        let x: u8 = foo().await;
        x + 5
    }
}

如第一章所见,async 体和其他 future 是惰性的:在运行之前不会做任何事情。运行 Future 最常见的方式是 .await 它。对 Future 调用 .await 时,会尝试将其运行至完成。若 Future 被阻塞,会让出当前线程的控制权。当可以进一步推进时,执行器会接管 Future 并恢复运行,使 .await 得以解析。

async 生命周期

与传统函数不同,接受引用或其他非 'static 参数的 async fn 返回的 Future 受参数生命周期的约束:

// This function:
async fn foo(x: &u8) -> u8 { *x }

// Is equivalent to this function:
fn foo_expanded<'a>(x: &'a u8) -> impl Future<Output = u8> + 'a {
    async move { *x }
}

这意味着从 async fn 返回的 future 必须在其非 'static 参数仍然有效时 .await。在调用函数后立即 .await future 的常见情况(如 foo(&x).await)下这不是问题。但若存储 future 或将其发送到另一任务或线程,这可能成为问题。

将带引用参数的 async fn 转为 'static future 的一种常见变通方法,是在 async 块内将参数与对 async fn 的调用捆绑在一起:

fn bad() -> impl Future<Output = u8> {
    let x = 5;
    borrow_x(&x) // ERROR: `x` does not live long enough
}

fn good() -> impl Future<Output = u8> {
    async {
        let x = 5;
        borrow_x(&x).await
    }
}

通过将参数移入 async 块,我们将其生命周期延长至与 good 调用返回的 Future 匹配。

async move

async 块和闭包允许使用 move 关键字,与普通闭包类似。async move 块会取得其引用变量的所有权,允许其超出当前作用域,但放弃与其他代码共享这些变量的能力:

/// `async` block:
///
/// Multiple different `async` blocks can access the same local variable
/// so long as they're executed within the variable's scope
async fn blocks() {
    let my_string = "foo".to_string();

    let future_one = async {
        // ...
        println!("{my_string}");
    };

    let future_two = async {
        // ...
        println!("{my_string}");
    };

    // Run both futures to completion, printing "foo" twice:
    let ((), ()) = futures::join!(future_one, future_two);
}

/// `async move` block:
///
/// Only one `async move` block can access the same captured variable, since
/// captures are moved into the `Future` generated by the `async move` block.
/// However, this allows the `Future` to outlive the original scope of the
/// variable:
fn move_block() -> impl Future<Output = ()> {
    let my_string = "foo".to_string();
    async move {
        // ...
        println!("{my_string}");
    }
}

在多线程执行器上 .await

请注意,使用多线程 Future 执行器时,Future 可能在线程间移动,因此 async 体中使用的任何变量必须能在线程间传递,因为任何 .await 都可能导致切换到新线程。

这意味着使用 Rc、&RefCell 或任何未实现 Send trait 的类型(包括未实现 Sync trait 的类型的引用)是不安全的。

(注意:只要这些类型在 .await 调用期间不在作用域内,仍可使用它们。)

类似地,在 .await 期间持有传统的非 future 感知锁不是好主意,可能导致线程池死锁:一个任务可能取出锁、.await 并让出给执行器,允许另一任务尝试取锁并造成死锁。为避免此问题,请使用 futures::lock 中的 Mutex,而非 std::sync 中的。

最后修改 August 23, 2026: 更新 (499855b16)