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
は executor
上で実行される必要があります。
// `block_on` は、提供された Future が実行されるまで、現在のスレッドをブロックします。 // 完了するまで現在のスレッドをブロックします。他の executers は、より複雑な動作を提供します。 // 複数の futures を同じスレッドにスケジューリングするような、より複雑な動作を提供します。 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
トレイトを実装している別の型の完了を待つことができます。
block_on
とは異なり、 .await
は現在のスレッドをブロックするのではなく、非同期に Future
が完了するのを待ちます。Future
が完了すると、他のタスクを実行できるようになります。
例えば、3 つの async fn
として learn_song
(訳注:歌を覚える)、sing_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());
}
しかし、この方法では、最高のパフォーマンスを発揮することはできません。なぜなら、一度に 1 つの処理しか行えないからです!
一方、私たちは歌を覚えてから歌うのは当然ですが、歌を覚えて歌を歌うのと同時に踊ることも可能です。
この方法を実現するためには、2つの独立した async fn
を作成し、それらを同時実行します。
async fn learn_and_sing() {
// 歌を学ぶまで待ってから歌います。
// ここでは `block_on` ではなく `.await` を使用し、スレッドのブロックを防いでいます。
// スレッドをブロックしないようにするため、`.await` を使用しています。これにより、`dance` を同時に行うことができます。
let song = learn_song().await;
sing_song(song).await;
}
async fn async_main() {
let f1 = learn_and_sing();
let f2 = dance();
// `join!` は `.await` のようなものだが、複数の futures を同時に待つことができます。
// `learn_and_sing` の future で一時的にブロックされた場合、 `dance` が現在のスレッドを引き継ぎます。
// もし `dance` がブロックされた場合、future が現在のスレッドを引き継ぎます。
// もし両方の未来がブロックされた場合、`learn_and_sing` が引き継ぐことができます。
// `async_main` がブロックされ、executor に引き継がれます。
futures::join!(f1, f2);
}
fn main() {
block_on(async_main());
}
この例では、歌を覚えるのは歌を歌う前でなければなりませんが、歌を覚えるのも歌を歌うのも踊る処理と同時に行うことができます。
もし、 learn_and_sing
の関数内で learn_song().await
ではなく block_on(learn_song())
を使用した場合、 learn_song
が実行されている間、スレッドは他の処理ができなくなります。これでは、同時に踊ることは不可能です。
.await
の場合、 learn_song
がブロックされた際に、他のタスクが現在のスレッドを引き継ぐことができます。したがって、同じスレッドで複数の Future
を同時に実行できます。