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
发起讨论 只看当前版本


暂无话题~