何度 async/await の解説を読んでもいまいちわかった感じがしないので, ともかく使ってみようと試行錯誤した成果です. エコシステムの進化が速すぎて情報集めに苦労しました. 個人的に一番 async/await の必要性が高かったファイル I/O を題材として取り上げます.
問題設定
コマンドライン引数としてあるディレクトリのパスが与えられるので, そのディレクトリ直下のファイルすべてについてチェックサムを計算せよ, という課題が与えらえたとしましょう. もちろん同期版のコードならすぐに書けます (ちょっと冗長ですが非同期版と比較しやすいようにしてあります).
use std::env;
fn main() {
use std::{fs, io::Read};
let directory = env::args().nth(1).unwrap();
let entries = fs::read_dir(&directory).unwrap();
for entry in entries {
let entry = entry.unwrap();
if entry.file_type().unwrap().is_file() {
let filepath = entry.path();
let mut file = fs::File::open(&filepath).unwrap();
let bytes = {
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).unwrap();
bytes
};
let checksum = bytes.iter()
.fold(0u8, |acc, b| acc.wrapping_add(*b));
println!("{:?}: {}", filepath.file_name().unwrap(), checksum);
}
}
}
ここで, 計算対象のファイルサイズがバラバラで, 小さなものからかなり大きなものまで存在するとしたらどうでしょう? このコードでは, 大きなファイルの I/O 待ちが発生している間, CPU はやることがありません. なんだかもったいないですね. 読み込みに時間がかかると思ったらそれはとりあえず後回しにして, 別の小さなファイルを読み込んでそのチェックサムを計算する, という風にできれば効率的な気がします. これこそが async/await が可能にしてくれる処理です.
async-std 版
std
とできるだけ同じ API を提供するという触れ込みの async-std
を使って書き換えてみます.
use std::env;
fn main() {
use async_std::{fs, io::ReadExt};
let directory = env::args().nth(1).unwrap();
let entries = std::fs::read_dir(directory).unwrap();
let mut handles = Vec::new();
for entry in entries {
let entry = entry.unwrap();
if entry.file_type().unwrap().is_file() {
let handle = async_std::task::spawn(async move {
let filepath = entry.path();
let mut file = fs::File::open(&filepath).await.unwrap();
let bytes = {
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).await.unwrap();
bytes
};
let checksum = bytes.iter()
.fold(0u8, |acc, b| acc.wrapping_add(*b));
println!("{:?}: {}", filepath.file_name().unwrap(), checksum);
});
handles.push(handle);
}
}
async_std::task::block_on(async move {
for handle in handles.into_iter() {
handle.await;
}
})
}
なんだか std::thread::spawn
するのと同じような感じで書けました. time
により時間を計ると, 同期版と非同期版で sys
時間はほぼ同じですが, 同期版ではおおよそ real
= user
+ sys
であるのに対して, 非同期版では明らかに real
< user
+ sys
になっています. ファイルサイズが小さい順にチェックサムが表示されますし, たぶんこれで意図通りに動いているはずです.
2020-04-26 追記 @blackenedgold さんに コメント でこのコードの問題点と改善案を教えていただきました. ありがとうございます!