Skip to content

Latest commit

 

History

History
118 lines (84 loc) · 4.82 KB

3-async await.md

File metadata and controls

118 lines (84 loc) · 4.82 KB

async/ .await

在第一章,我们了解了async/.await的简要信息。在这一张,我们将会讨论async/.await更加详细的细节,并解释它怎样工作,它与传统的Rust程序的不同。

async/.await是rust语法中特殊的一部分,它可以让当需要等待一个操作完成时,让出当前线程的控制权来处理其它代码,而不是阻塞当前线程变得可能。

有两个主要方法来使用asyncasync fnasync 块。每个方法返回的值都实现了Future trait。

// `foo()` 返回的类型实现了`Future<Output = u8>`.
// `foo().await`将会得到一个`u8`类型的结果.
async fn foo() -> u8 { 5 }

fn bar() -> impl Future<Output = u8> {
  	// 这个`async` 块的结果实现了 `Future<Output = u8>` trait.
    async {
        let x: u8 = foo().await;
        x + 5
    }
}

如我们在第一章所视的一样,async 体和其他future是惰性的:它们运行前都不会做任何事。运行一个Future最常见的方式是.await。当一个Future调用了.await,它将会尝试完成运行。如果这个Future阻塞,它将会让出当前线程的控制权。当Future可以更进一步运行时,Future 将会被执行器(executor)挑选出来恢复运行,.await帮我解决了这些流程。

async 生命周期

与传统的函数不同,async fn 接受一个引用或者一个非'static'参数,返回的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 必须在它被.await时,它的非'static'参数必须有效。通常情况下,调用函数后马上.await future(例如foo(&x).await),这将不是一个问题。然而,如果存储future或者发送它到另一个任务或者线程中,这将是一个问题。

一个通常的解决方法是将一个带有引用参数的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 块中,我们扩展了它的生命周期,以匹配调用good返回的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`块可以访问相同的本地变量,只要他们在变量的范围内执行。
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:
/// 只有一个`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可能导致切换到新的线程中。

这就意味着使用RcRefCell或者其他没有实现Send trait的类型是不安全的,包括未实现Sync trait的类型的引用。

(警告:只要不在调用.await的范围内,就可以使用这些类型。)

相似地,在.await中持有一个传统的非 future 感知的锁不是一个好主意,因为它可能造成线程池锁住:一个任务可以获取一个锁,.await然后让出到执行器,允许其他任务尝试获取所并导致死锁。为了避免这种情况,使用futures::lock中的Mutex 来替代std::sync中的Mutex