async
/.await
在第一章节,我们简要介绍了async
/.await
,并用它来构建一个简单的服务器。本章将更为详细讨论async
/.await
的它如何工作以及如何async
代码与传统的 Rust 程序不同。
async
/.await
是 Rust 语法的特殊部分,它使得可以 yield 对当前线程的控制而不是阻塞,从而允许在等待操作完成时,其他代码可以运行。
async
有两种主要的使用方式:async fn
和async
代码块。每个返回一个实现Future
trait:
#![allow(unused_variables)] fn main() { // `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
阻塞,它将 yield(归还)当前线程的控制。当 Future 可以更进一步时,Future
将由 executor 接管并恢复运行,允许.await
搞定这个 future。
async
Lifetimes
与传统函数不同,async fn
会接受一个引用,或其他非'static
的参数,并返回一个Future
,这受到这些个参数生命周期的限制:
#![allow(unused_variables)] fn main() { // 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
ed,期间它的非'static
参数仍然有效。在通常情况下,在调用.await
之后,会立即执行 (如foo(&x).await
),而这并不是问题。但是,如果存储这个 Future,或将其发送到另一个任务或线程,则可能会出现问题。
一种常见的变通方法是,将引用作为参数的async fn
函数转换成一个'static
Future,具体是在一个async
代码块里面,将这个参数与这async fn
的调用捆绑在一起:
#![allow(unused_variables)] fn main() { 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 move
代码块将拥有,对其引用的变量的所有权,从而使生命周期超过当前范围,但放弃了与其他代码共享这些变量的能力:
#![allow(unused_variables)] fn main() { /// `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); } } }
.await
ing on a Multithreaded Executor
请注意,使用一个多线程的Future
executor,一个Future
可能会在线程之间移动,因此在async
主体内的任何使用变量,必须能够在线程之间移动,正如任一.await
都具有在一个 switch,就去到新线程的潜在结果。
这意味着,使用Rc
,&RefCell
或任何其他未实现Send
trait,包括那些未实现Sync
trait 的类型的引用都是不安全。
(注意:在调用.await
期间,只要它们不在范围内,就有可能使用这些类型)
同样,想在一个.await
上,搞个传统的 non-futures-aware 锁,也不是一个好主意,因为它可能导致线程池锁定:一项任务可以拿一个锁,之后.await
并 yield 到 executor,而再允许另一个任务尝试获取该锁,也就会导致死锁。为避免这种情况,请在futures::lock
使用Mutex
,而不是std::sync
里的那个。