async/.await初步

async/.await是Rust内置语法,用于让异步函数编写得像同步代码。async将代码块转化成 实现了Future特质的状态机。使用同步方法调用阻塞函数会阻塞整个线程,但阻塞Future只会 让出(yield)线程控制权,让其他Future继续执行。

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


# #![allow(unused_variables)]
#fn main() {
async fn do_something() { ... }
#}

async fn函数返回实现了Future的类型。为了执行这个Future,我们需要执行器(executor)

// `block_on` 将阻塞当前的线程,直到 `future` 运行完成.
// 其他执行器提供了更复杂的特性,比如将多个 `future` 安排到同一个线程上面.
use futures::executor::block_on;

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

fn main() {
    let future = hello_world(); // 没有输出.
    block_on(future); // `future` 运行,输出 "hello, world!".
}

async fn函数中, 你可以使用.await来等待其他实现了Future特质的类型完成,例如 另外一个async fn的输出。和block_on不同,.await不会阻塞当前线程,而是异步地等待 future完成,在当前future无法进行下去时,允许其他任务运行。

举个例子,想想有以下三个async fn: learn_song, sing_songdance


# #![allow(unused_variables)]
#fn main() {
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() {
    // 要唱歌必须得先学会歌曲.
    // 我们这里使用 `.await` 而不是 `block_on` 来
    // 防止线程阻塞, 这样也可以同时跳舞.
    let song = learn_song().await;
    sing_song(song).await;
}

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

    // `join!` 类似 `.await`,但是可以同时等待多个 `future` 执行完成.
    // 如果我们 `learn_and_sing` 这个 `future` 被阻塞, 那么 `dance`
    // 这个 `future` 将接管当前的线程. 如果 `dance` 被阻塞, 那么 `learn_and_sing`
    // 就可以重新开始. 如果这个两个 `future` 都被阻塞, 那么 `async_main`
    // 也将被阻塞并让位给执行程序.
    futures::join!(f1, f2);
}

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

这个示例里,唱歌之前必须要学习唱这首歌,但是学习唱歌和唱歌都可以和跳舞同时发生。如果我们 用了block_on(learning_song())而不是learn_and_sing中的learn_song().await, 那么当learn_song在执行时线程将无法做别的事,这也使得无法同时跳舞。但是通过.await 执行learn_song的future,我们就可以在learn_song阻塞时让其他任务来掌控当前线程。 这样就可以做到在单线程并发执行多个future到完成状态。

现在,你已经学会了async/await基础,现在我们来试着写一个例子吧。