3.1. async/await!

未匹配的标注

async/await!

在[第一章]中,我们简要介绍了async/ await!并使用它来构建一个简单的服务器。本章将更详细地讨论async/ await!解释它如何工作以及async代码与传统Rust程序的不同之处。

async/ await!是Rust语法的特殊部分,可以控制当前线程而不阻塞,允许其他代码在等待操作完成时取得进展。

async有三种主要的使用方法:async fnasync块async闭包。每个返回一个实现Future特征的值:

// `foo()` returns a type that implements `Future<Output = u8>`.
// `await!(foo())` 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 = await!(foo());
        x + 5
    }
}

fn baz() -> impl Future<Output = u8> {
    // This `async` closure, when called, returns a type that
    // implements `Future<Output = u8>`
    let closure = async |x: u8| {
        await!(bar()) + x
    };
    closure(5)
}

正如我们在第一章中看到的那样,async和其他Future是懒惰的:它们在运行之前什么都不做。运行Future的最常见方式是await!它。当在Future上调用await!时,它将尝试运行以完成它。如果Future被阻止,它将让出当前线程。当可以取得更多进展时,执行者将获取 Future并将继续运行,以便await!解决。

async 生命周期

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

// This function:
async fn foo(x: &u8) -> u8 { *x }

// Is equivalent ot this function:
fn foo<'a>(x: &'a u8) -> impl Future<Output = ()> + 'a {
    async { *x }
}

这意味着从async fn返回Future必须await!,且当非'static参数时仍然有效。常见情况中,在调用函数之后立即await! Future(如await!(foo(&x))),这不是问题。但是,如果存储Future或将其发送到另一个任务或线程,这可能是一个问题。

将带有引用作为参数的 async fn转换为'static Future的一个常见解决方法是将参数与对块async fn调用捆绑在一起放进async块内:

async fn foo(x: &u8) -> u8 { *x }

fn bad() -> impl Future<Output = ()> {
    let x = 5;
    foo(&x) // ERROR: `x` does not live long enough
}

fn good() -> impl Future<Output = ()> {
    async {
        let x = 5;
        await!(foo(&x))
    }
}

通过将参数移动到async块中,我们将其生命周期延长到与调用foo返回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 foo() {
    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 ((), ()) = join!(future_one, future_two);
}

/// `async move` block:
///
/// Only one `async` block can access captured variables, since they are
/// moved into the `Future` generated by the `async` block. However,
/// this allows the `Future` to outlive the original scope of the variable:
fn foo() -> 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中的。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~