Posted at

ついにstableになったRustのasync/awaitを試してみる


:tada:祝!Rust 1.39.0

ついに、Rust 1.39.0になりました!そこで、既に他の方も書かれているかもしれないですが、安定化したasync/.awaitを試してみたいと思います(なお、筆者はRust及び非同期初心者です)。


基本的なasync/.await

最も基本的な使い方は、

async fn f() {

let future = /* impl Futureな何か */;
future.await;
}

です。簡単!と思ったのですが、.awaitasync関数またはブロックの中でしか呼べず、また.awaitをしないと実際には処理を開始してくれないので、(簡単には)asyncでないmain関数から呼び出せません...困った。


futures crateとの併用

futures 0.3を使えば簡単にasync/.awaitできるみたいなので、試してみました。


基本

まず、async fnで非同期処理したい関数を作成します(ここまではstdでできます。)

async fn long_task(task_id: usize) {

println!("Task {} on {:?}", task_id, std::thread::current().id());
std::thread::sleep(std::time::Duration::from_secs(1));
println!("Task {} done", task_id);
}

続いてこれをmain関数から呼び出すわけですが、ここでfutures::executor::block_onを使います。

fn main() {

let task = long_task(0); // ここではまだ実行されない
futures::executor::block_on(task); // ここで実行される(.awaitの代わりのようなもの)
}

これで main関数と同じスレッドから long_task関数が呼び出せました。


別スレッドでの処理

非同期処理するからには異なるスレッドで実行したいので、それを試してみます。そのためにまずfuturesthread-pool featureを加えて、futures::executor::ThreadPoolを作ります。ビルダーから作っても良いですし、単にnewするだけでも大丈夫です。

別スレッドで処理してもらうためにspawn系を呼び出すのですが、単にspawnしてそのまま終わってしまうとやはり実行されない(ことがある)ので、後でjoinするために今回はspawn_with_handleを用いました。その結果、以下のような感じになりました。

use futures::{

executor::{block_on, ThreadPool},
future,
task::SpawnExt,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut pool = ThreadPool::new()?;
let mut futures = Vec::new();
for i in 0..20 {
futures.push(pool.spawn_with_handle(long_task(i))?);
}
block_on(future::join_all(futures));
Ok(())
}


感想

現状、stdだけだと非同期処理を書くのはちょっと大変そうです。もちろん、futures crateもRustで書かれているので頑張れば以上のことをstdだけでできるはずですが、ソースコード見ても私にはなかなか難しそうだったので、素直に外部crateを使うことにします。ありがたや~