3.1. async/await!
async
/await!
#
在 [第一章] 中,我们简要介绍了 async/ await!
并使用它来构建一个简单的服务器。本章将更详细地讨论 async/ await!
解释它如何工作以及 async
代码与传统 Rust 程序的不同之处。
async/ await!
是 Rust 语法的特殊部分,可以控制当前线程而不阻塞,允许其他代码在等待操作完成时取得进展。
async
有三种主要的使用方法:async fn
,async块
和 async闭包
。每个返回一个实现 Future
特征的值:
// `foo()` returns a type that implements `Future<Output = u8>`.
// `await!(foo())` 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 = await!(foo());
x + 5
}
}
fn baz() -> impl Future<Output = u8> {
// This `async` closure, when called, returns a type that
// implements `Future<Output = u8>`
let closure = async |x: u8| {
await!(bar()) + x
};
closure(5)
}
正如我们在第一章中看到的那样,async
和其他 Future
是懒惰的:它们在运行之前什么都不做。运行 Future
的最常见方式是 await!
它。当在 Future
上调用 await!
时,它将尝试运行以完成它。如果 Future
被阻止,它将让出当前线程。当可以取得更多进展时,执行者将获取 Future
并将继续运行,以便 await!
解决。
async
生命周期#
async fn
与传统函数不同,带引用或其他非'static
参数的,返回一个受参数生命周期限制的 Future
:
// This function:
async fn foo(x: &u8) -> u8 { *x }
// Is equivalent ot this function:
fn foo<'a>(x: &'a u8) -> impl Future<Output = ()> + 'a {
async { *x }
}
这意味着从 async fn
返回 Future
必须 await!
,且当非'static 参数时仍然有效。常见情况中,在调用函数之后立即 await!
Future
(如 await!(foo(&x))
),这不是问题。但是,如果存储 Future
或将其发送到另一个任务或线程,这可能是一个问题。
将带有引用作为参数的 async fn
转换为'static
Future
的一个常见解决方法是将参数与对块 async fn
调用捆绑在一起放进 async
块内:
async fn foo(x: &u8) -> u8 { *x }
fn bad() -> impl Future<Output = ()> {
let x = 5;
foo(&x) // ERROR: `x` does not live long enough
}
fn good() -> impl Future<Output = ()> {
async {
let x = 5;
await!(foo(&x))
}
}
通过将参数移动到 async
块中,我们将其生命周期延长到与调用 foo
返回 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 foo() {
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 ((), ()) = join!(future_one, future_two);
}
/// `async move` block:
///
/// Only one `async` block can access captured variables, since they are
/// moved into the `Future` generated by the `async` block. However,
/// this allows the `Future` to outlive the original scope of the variable:
fn foo() -> impl Future<Output = ()> {
let my_string = "foo".to_string();
async move {
...
println!("{}", my_string);
}
}
await!
在多线程执行者#
请注意,在使用多线程 Future
执行者时,Future
可能在线程之间移动,因此在 async
主体中使用的任何变量都必须能够在线程之间传输,因为任何 await!
变量都可能导致切换到新线程。
这意味着使用 Rc,&RefCell
是不安全的或任何其他没实现 Send
特征的类型,包括引用未实现 Sync
特质的类型。
(警告:只要在调用 await!
期间它们不在范围内,就可以使用这些类型。)
类似地,在 await!
期间持有一个传统的非 Future
感知的锁不是一个好主意,因为它可能导致线程池锁定:一个任务可以取出锁,await!
并让出执行者,允许另一个任务尝试获取锁导致死锁。要避免这种情况,请使用 futures::lock
中的 Mutex
而不是 std::sync
中的。