async/.await 入门

async/.await 是 Rust 的内置工具,使得你可以如同写同步代码一样编写异步程序。 async 会将一个代码块转化为一个实现了名为 Future 特征的状态机。 虽然在同步方法中调用阻塞函数会阻塞整个线程,但阻塞的 Future 会让出线程控制权, 允许其它 Future 运行。

让我们在 Cargo.toml 文件中添加一些依赖项。

[dependencies]
futures = "0.3"

你可以使用 async fn 语法来创建一个异步函数:

#![allow(unused)]
fn main() {
async fn do_something() { /* ... */ }
}

async fn 的返回值是一个 Future。需有一个执行器,Future 才可执行。

// `block_on` blocks the current thread until the provided future has run to
// completion. Other executors provide more complex behavior, like scheduling
// multiple futures onto the same thread.
use futures::executor::block_on;

async fn hello_world() {
    println!("hello, world!");
}

fn main() {
    let future = hello_world(); // Nothing is printed
    block_on(future); // `future` is run and "hello, world!" is printed
}

async fn 中,你可以使用 .await 等待另一个实现了 Future 特征的类型完成, 例如另一个 async fn 的返回值。与 block_on 不同,.await 不会阻塞当前线程, 而是在当前 future 无法取得进展时允许其它任务继续运行,同时在异步状态等待它的完成。

例如,现在我们有三个异步函数,分别是 learn_songsing_song 以及 dance

async fn learn_song() -> Song { /* ... */ }
async fn sing_song(song: Song) { /* ... */ }
async fn dance() { /* ... */ }

一种是,学唱、唱和跳舞以阻塞的方式的执行:

fn main() {
    let song = block_on(learn_song());
    block_on(sing_song(song));
    block_on(dance());
}

然而,这种方式并未发挥出最好的性能——因为我们每次只做了一件事。 显然,只有在学会唱歌后才能去唱,但在我们学习或唱歌时,却可以同时跳舞的。 要实现这个,我们可以分别创建两个 async fn 来并发的执行:

async fn learn_and_sing() {
    // Wait until the song has been learned before singing it.
    // We use `.await` here rather than `block_on` to prevent blocking the
    // thread, which makes it possible to `dance` at the same time.
    let song = learn_song().await;
    sing_song(song).await;
}

async fn async_main() {
    let f1 = learn_and_sing();
    let f2 = dance();

    // `join!` is like `.await` but can wait for multiple futures concurrently.
    // If we're temporarily blocked in the `learn_and_sing` future, the `dance`
    // future will take over the current thread. If `dance` becomes blocked,
    // `learn_and_sing` can take back over. If both futures are blocked, then
    // `async_main` is blocked and will yield to the executor.
    futures::join!(f1, f2);
}

fn main() {
    block_on(async_main());
}

在这个例子中,学习唱歌必须在唱歌之前,但学唱和唱歌都可与跳舞这个行为同时发生。 如果我们在 learn_and_sing 中使用 block_on(learn_song()) , 而不是 learn_song().await,它将在执行时阻塞主进程直至学歌完成,而无法同时跳舞。 通过 .await,使得在学歌这一行为发生阻塞时,让出主进程控制权。 这使得可以在同一线程中同时运行多个 future 并驱使之完成。