async
/.await
在 第一章中我们简单领略了下async
/await
并用它们建立了一个简单的服务器。这一章会更深入地讨论async
/.await
的细节,解释它们如何工作,而async
代码和传统的Rust程序又有怎样的不同。
async
/.await
是使让出当前线程的控制权而非阻塞成为可能的语法,让程序可以在等待某个行为完成的同时允许其他代码运行。
有两个使用async
的主要方法:async fn
和async
块(block)。它们都会返回一个实现了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
阻塞了,就让出当前线程的控制权。而可以继续执行时,Future
就会被executor取出并回复运行,让.await
得以解析(resolve)。
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 或 将其发送给了另一个task或线程,就可能出问题。
一个将含引用参数的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
块中,我们延长了它的生命周期,使得它匹配了调用call
时返回的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);
}
}
多线程Executor的.await
需要注意的是,当使用一个多线程Future
executor时,一个Future
可能会在线程间转移,因此async
代码体重的任何变量同样有在线程间转移的可能,因为.await
有可能导致切换至新的线程。
也就是说,使用Rc
,&RefCell
或者其他没有实现Send
trait的类型,以及 没有实现Sync
的类型 的引用,都是不安全的。
(另:如果不是在调用.await
时使用还是可以的。)
类似地,不应该在.await
过程中使用一个传统的,非future感知的锁(non-futures-aware lock),因为其可能导致线程池的锁定:一个task除去锁,进行await
并向executor让出,让另一个task尝试除去锁,因而造成死锁。为了避免这个现象,请使用futures::lock
中的Mutex
,而不是std::sync
。