async/.await

第一章,我们简单介绍了async/.await,并且用它构建一个简单的服务器。这一章会详细 讨论async/.await,解释它如何工作以及async代码如何和传统Rust程序不同。

async/.await是特殊的Rust语法,使得让出当前线程控制权成为可能,而不是阻塞它,也允许 其他代码在等待一个车操作完成时取得进展。

有两种主要的方法使用async: async fnasync块。两种方法都返回一个实现了Future 特质的值:


# #![allow(unused_variables)]

#fn main() {
// `foo()` 返回一个实现了 `Future<Output = u8>` 的类型.
// `foo().await` 将返回类型为 `u8` 的值.
async fn foo() -> u8 { 5 }

fn bar() -> impl Future<Output = u8> {
    // 这个 `async` 区域返回一个实现了 `Future<Output = u8>` 的类型.
    async {
        let x: u8 = foo().await;
        x + 5
    }
}
#}

就像我们在第一章中看到,async体以及其他future类型是惰性的:除非它们运行起来,否则它们 什么都不做。运行Future最常见的方法是.await它。当.awaitFuture上调用时,它会 尝试把future跑到完成状态。如果Future被阻塞了,它会让出当前线程的控制权。能取得进展时, 执行器就会捡起这个Future并继续执行,让.await求解。

async生命周期

和传统函数不同,async fn会获取引用以及其他'static生命周期参数,并返回被这些参数的 生命周期约束的Future


# #![allow(unused_variables)]
#fn main() {
// 这是一个 `async` 函数:
async fn foo(x: &u8) -> u8 { *x }

// 相当于这个普通函数:
fn foo_expanded<'a>(x: &'a u8) -> impl Future<Output = u8> + 'a {
    async move { *x }
}
#}

这意味着这些future被async fn函数返回后必须要在它的非'static参数仍然有效时.await。 在通常的场景中,future在函数调用后马上.await(例如foo(&x).await),并不会有 大问题。然而,如果储存了这些future或者把它发送到其他的任务或者线程,那就有问题了。

一个常用的规避方法以把带有引用参数的async fn转化成一个'staticfuture是把这些参数 和应用的async fn函数调用封装到async块中:


# #![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块中,我们把它的生命周期扩展到了匹配调用foo函数返回的Future的 生命周期。

async move

async块和闭包允许使用move关键字,这和普通的闭包一样。一个async move块会获取 所指向变量的所有群,允许它超长存活(outlive)当前作用域,但是放弃了与其他代码共享这些 变量的能力:


# #![allow(unused_variables)]
#fn main() {
/// `async` 区域:
///
/// 多个 `async` 区域可以访问相同的本地变量,
/// 只要它们在变量的作用域内执行.
async fn blocks() {
    let my_string = "foo".to_string();

    let future_one = async {
        // ...
        println!("{}", my_string);
    };

    let future_two = async {
        // ...
        println!("{}", my_string);
    };

    // 运行两个 `future`,输出两次 "foo":
    let ((), ()) = futures::join!(future_one, future_two);
}

/// `async move` 区域:
///
/// 只有一个 `async move` 区域可以访问同一个被捕获的变量, 
/// 因为被捕获的变量已经移动到 `async move` 生成的 `future` 中:
fn move_block() -> impl Future<Output = ()> {
    let my_string = "foo".to_string();
    async move {
        // ...
        println!("{}", my_string);
    }
}
#}

在多线程执行器中.await

提醒一下,在使用多线程的Future执行器时,一个Future可能在线程间移动,所以任何在 async体重使用的变量必须能够穿过线程,所以任何.await都有可能导致切换到新线程。

这意味着使用Rc&RefCell或者其他没有实现Send特质的类型是不安全的,包括那些指向 没有Sync特质类型的引用。

(告示:使用这些类型是允许的,只要他们不是在调用.await的作用域内。)

类似的,横跨.await持有一个非future感知的锁这种做法是很不好的,因为它能导致整个线程池 锁上:一个任务可能获得了所,.await然后让出到执行器,允许其他任务尝试获取所并导致死锁。 为了避免这种情况,使用futures::lock里的Mutex类型比起std::sync更好。