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中的。
Rust 异步编程
关于 LearnKu