async/.await

第一章节,我们简要介绍了async/.await,并用它来构建一个简单的服务器。本章将更为详细讨论async/.await的它如何工作以及如何async代码与传统的 Rust 程序不同。

async/.await是 Rust 语法的特殊部分,它使得可以 yield 对当前线程的控制而不是阻塞,从而允许在等待操作完成时,其他代码可以运行。

async有两种主要的使用方式:async fnasync代码块。每个返回一个实现Futuretrait:

  1. {{#include ../../examples/03_01_async_await/src/lib.rs:async_fn_and_block_examples}}

正如我们在第一章所看到的,async主体和其他 Future 是懒惰的:它们在运行之前什么也不做。最常见的,运行一个Future的方式是.await它。当在Future上调用.await的时候,它将尝试运行它,以完成操作。如果Future阻塞,它将 yield(归还)当前线程的控制。当 Future 可以更进一步时,Future将由 executor 接管并恢复运行,允许.await搞定这个 future。

async Lifetimes

与传统函数不同,async fn会接受一个引用,或其他非'static的参数,并返回一个Future,这受到这些个参数生命周期的限制:

  1. {{#include ../../examples/03_01_async_await/src/lib.rs:lifetimes_expanded}}

这意味着,来自一个async fn的 Future 必须被.awaited,期间它的非'static参数仍然有效。在通常情况下,在调用.await之后,会立即执行 (如foo(&x).await),而这并不是问题。但是,如果存储这个 Future,或将其发送到另一个任务或线程,则可能会出现问题。

一种常见的变通方法是,将引用作为参数的async fn函数转换成一个'static Future,具体是在一个async代码块里面,将这个参数与这async fn的调用捆绑在一起:

  1. {{#include ../../examples/03_01_async_await/src/lib.rs:static_future_with_borrow}}

通过将参数移到async代码块,我们延长了其生命周期,以匹配,来自good的调用返回的Future

async move

async代码块和闭包允许move关键字,很像普通的闭包。一个async move代码块将拥有,对其引用的变量的所有权,从而使生命周期超过当前范围,但放弃了与其他代码共享这些变量的能力:

  1. {{#include ../../examples/03_01_async_await/src/lib.rs:async_move_examples}}

.awaiting on a Multithreaded Executor

请注意,使用一个多线程的Future executor,一个Future可能会在线程之间移动,因此在async主体内的任何使用变量,必须能够在线程之间移动,正如任一.await都具有在一个 switch,就去到新线程的潜在结果。

这意味着,使用Rc&RefCell或任何其他未实现Send trait,包括那些未实现Synctrait 的类型的引用都是不安全。

(注意:在调用.await期间,只要它们不在范围内,就有可能使用这些类型)

同样,想在一个.await上,搞个传统的 non-futures-aware 锁,也不是一个好主意,因为它可能导致线程池锁定:一项任务可以拿一个锁,之后.await并 yield 到 executor,而再允许另一个任务尝试获取该锁,也就会导致死锁。为避免这种情况,请在futures::lock使用Mutex,而不是std::sync里的那个。