在第一章,我们了解了async
/.await
的简要信息。在这一张,我们将会讨论async
/.await
更加详细的细节,并解释它怎样工作,它与传统的Rust程序的不同。
async
/.await
是rust语法中特殊的一部分,它可以让当需要等待一个操作完成时,让出当前线程的控制权来处理其它代码,而不是阻塞当前线程变得可能。
有两个主要方法来使用async
:async fn
和 async
块。每个方法返回的值都实现了Future
trait。
// `foo()` 返回的类型实现了`Future<Output = u8>`.
// `foo().await`将会得到一个`u8`类型的结果.
async fn foo() -> u8 { 5 }
fn bar() -> impl Future<Output = u8> {
// 这个`async` 块的结果实现了 `Future<Output = u8>` trait.
async {
let x: u8 = foo().await;
x + 5
}
}
如我们在第一章所视的一样,async
体和其他future
是惰性的:它们运行前都不会做任何事。运行一个Future
最常见的方式是.await
。当一个Future
调用了.await
,它将会尝试完成运行。如果这个Future
阻塞,它将会让出当前线程的控制权。当Future
可以更进一步运行时,Future
将会被执行器(executor)挑选出来恢复运行,.await
帮我解决了这些流程。
与传统的函数不同,async fn
接受一个引用或者一个非'static'
参数,返回的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 必须在它被.await
时,它的非'static'
参数必须有效。通常情况下,调用函数后马上.await
future(例如foo(&x).await
),这将不是一个问题。然而,如果存储future或者发送它到另一个任务或者线程中,这将是一个问题。
一个通常的解决方法是将一个带有引用参数的async fn
转化为一个'static'
的future,把async fn
要调用的参数绑定到一个async
块中。
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` block:
///
/// Multiple different `async` blocks can access the same local variable
/// so long as they're executed within the variable's scope
/// 多个不同的`async`块可以访问相同的本地变量,只要他们在变量的范围内执行。
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:
/// 只有一个`async move`块能够访问相同的被俘的变量,
/// 因为被俘的变量被移动到`async move`块生成的`Future`中了。
fn move_block() -> impl Future<Output = ()> {
let my_string = "foo".to_string();
async move {
// ...
println!("{}", my_string);
}
}
记住,当使用一个多线程Future
执行器,一个Future
可能在多个线程中移动,因此在async
体中使用的任何变量必须可以在多个线程中传播,因为任何.await
可能导致切换到新的线程中。
这就意味着使用Rc
、RefCell
或者其他没有实现Send
trait的类型是不安全的,包括未实现Sync
trait的类型的引用。
(警告:只要不在调用.await
的范围内,就可以使用这些类型。)
相似地,在.await
中持有一个传统的非 future
感知的锁不是一个好主意,因为它可能造成线程池锁住:一个任务可以获取一个锁,.await
然后让出到执行器,允许其他任务尝试获取所并导致死锁。为了避免这种情况,使用futures::lock
中的Mutex
来替代std::sync
中的Mutex
。