21-async/await
3 分钟阅读
译文 · 基于 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 中的。