祝!Rust 1.39.0
ついに、Rust 1.39.0になりました!そこで、既に他の方も書かれているかもしれないですが、安定化したasync/.awaitを試してみたいと思います(なお、筆者はRust及び非同期初心者です)。
基本的なasync/.await
最も基本的な使い方は、
async fn f() {
let future = /* impl Futureな何か */;
future.await;
}
です。簡単!と思ったのですが、.awaitはasync関数またはブロックの中でしか呼べず、また.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);
}
追記: 今更ながらちゃんとドキュメントをみたら std::thread::sleepはasync fn内では使ってはいけない と書いてありました。同様のことを行うには各runtime(tokioやasync_std、futuresの場合はfutures-timerというクレート)のDelayを使うとできるそうです。
続いてこれをmain関数から呼び出すわけですが、ここでfutures::executor::block_onを使います。
fn main() {
let task = long_task(0); // ここではまだ実行されない
futures::executor::block_on(task); // ここで実行される(.awaitの代わりのようなもの)
}
これで main関数と同じスレッドから long_task関数が呼び出せました。
別スレッドでの処理
非同期処理するからには異なるスレッドで実行したいので、それを試してみます。そのためにまずfuturesにthread-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を使うことにします。ありがたや~