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