async/.await 入门

async/.await是Rust中用来像同步代码一样编写异步代码的工具。async会将一个代码块转换为一个实现了Futuretrait的状态机。尽管在同步方法中调用阻塞函数(blocking function)会使整个线程阻塞,被阻塞的Future会让出线程的控制权,好让其他的Future能够运行。

先在Cargo.toml文件中添加一些依赖:

[dependencies]
futures = "0.3"

使用async fn的语法就可以创建一个异步函数了:


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

async fn返回值是一个Future,一个Future必须通过executor来运行。

// `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来等待另一个实现了Futuretrait的类型的完成,比如另一个async fn的输出。与block_on不同,.await不会阻塞当前的线程,而是异步地等待该future的完成,使得当这个future没有进展时其他任务仍然可以运行。

举个例子,假设有三个async fnlearn_songsing_song,和dance

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

一种方式是令learn,sing和dance分别被阻塞:

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

但这远非最好的方式——因为一次只能做一件事!显然要想sing,我们就必须先learn song,而dance是可以与learn和sing同时进行的。要这样做的话,可以创建两个可并行运行的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 song一定会发生在sing song之前,但是learn和sing都可以和dance同时进行。如果我们在learn_and_sing中使用block_on(learn_song())而非learn_song().await,线程就不能在learn_song的同时做其他的事情了。(后者)使得我们可以同时进行dance。通过.await learn_song的future,我们让其他的任务可在learn_song被阻塞时接管当前线程,这样就能在同一线程上并发运行多个future直至其完成了。

现在你已经学习到了async/await的基础知识了,来个例子试试吧。