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の内部では、別のasync fnの出力など、Futureトレイトを実装する別の型の完了を待つために.awaitを使用できます。block_onとは異なり、.awaitは現在のスレッドをブロックせず、代わりに future が完了するのを非同期に待ちます。これにより、future が現在進行できない場合に他のタスクを実行できるようになります。
たとえば、learn_song、sing_song、danceという 3 つのasync fnがあるとします。
async fn learn_song() -> Song { /* ... */ }
async fn sing_song(song: Song) { /* ... */ }
async fn dance() { /* ... */ }
学び、歌い、踊るための 1 つの方法は、これらを個別にブロックすることです。
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() {
// 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でlearn_song().awaitではなくblock_on(learn_song())を使用した場合、learn_songの実行中はそのスレッドで他に何もできません。これにより、同時に踊ることは不可能になります。learn_song future に対して.awaitすることで、learn_songがブロックされている場合に他のタスクが現在のスレッドを引き継げるようになります。これにより、同じスレッド上で複数の future を並行して完了まで実行できるようになります。