async / .await 入門!

async/.awaitは通常の同期コードの用に見える非同期関数を作成するための Rust のビルドインツールです。 asyncはコードブロックをFutureトレイトを実装しているステートマシンに変換するものです。 一方、同期メソッドでブロッキング関数を呼び出すとスレッド全体がブロックされてしまいますが、ブロックされたFutureはスレッドの制御をもたらし、他のFutureを実行できるようにします。

非同期関数を作成するには次のasync fn構文を使用できます。


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

async fnによってこの関数の返り値はFutureになります。 Futureは次のようにエグゼキューターで実行する必要があります。

// block_onは提供されたfutureが完了するまで現在のスレッドをブロックします。
// 他のエグゼキューターは、同じスレッドに複数のfutureをスケジュールするなど、
// より複雑な動作を提供します。
use futures::executor::block_on;

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

fn main() {
    let future = hello_world(); // ここでは何もprintされない
    block_on(future); // futureが動き"hello, world!"が表示される
}

async fn内では.awaitを使うことで、ほかのFutureトレイトを実装する別の型の完了を待つことができます。block_onとは異なり、.awaitは現在のスレッドをブロックしません、代わりに、Futureが完了するのを非同期で待機し、Futureが現在進行できないときは他のタスクを実行できるようにします。

例として、3 つのasync fnを考えてみましょう。learn_song, sing_song, danceです。


# #![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());
}

ただ、この方法では最高のパフォーマンスを実現しているわけではありません。一つのことしか実行してないからね!

明らかに、歌を歌うには歌を学ぶ必要があります。しかし、歌を学んだあとに歌うのと、同時に踊ることも可能ですよね?

これを行うには、同時に実行できる 2 つの独立した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_singlearn_song().awaitではなくblock_on(learn_son())を使ってしまうと、スレッドはしばらくの間他のことを行うことができなくなり、同時に踊ることを不可能にします。

今学習した、async / await の例を試してみましょう!