はじめに
非同期プログラミングでは「一定時間待つ」「定期的に処理を実行する」といった時間ベースの制御が頻繁に必要になります。Rustの非同期ランタイムである Tokio は、tokio::time モジュールを通じて豊富なタイマー機能を提供しています。
この記事では以下の内容を解説します。
tokio::time::sleepで非同期スリープtokio::time::intervalで定期実行tokio::time::timeoutでタイムアウト処理
事前準備
Cargo.toml に Tokio を追加します。time フィーチャーを有効にする必要があります。
[dependencies]
tokio = { version = "1", features = ["full"] }
features = ["full"] を指定すると time を含むすべての機能が有効になります。
tokio::time::sleep で非同期スリープ
基本的な使い方
tokio::time::sleep は指定した時間だけ非同期に待機する関数です。スレッドをブロックしないのがポイントです。
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
println!("処理開始");
sleep(Duration::from_secs(2)).await;
println!("2秒後に再開");
}
std::thread::sleep との違いは、thread::sleep がスレッド全体をブロックするのに対し、tokio::time::sleep は現在のタスクだけを一時停止し、他のタスクが実行できる点です。
複数タスクでの挙動を確認する
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
let task1 = tokio::spawn(async {
println!("タスク1: 開始");
sleep(Duration::from_secs(2)).await;
println!("タスク1: 完了");
});
let task2 = tokio::spawn(async {
println!("タスク2: 開始");
sleep(Duration::from_secs(1)).await;
println!("タスク2: 完了");
});
let _ = tokio::join!(task1, task2);
}
実行すると タスク2 が先に完了します。2つのタスクが並行して動いているため、スリープ中に他のタスクへ制御が渡されているのがわかります。
tokio::time::interval で定期実行
Interval の基本
tokio::time::interval は指定した間隔で繰り返しティックするタイマーを作成します。
use tokio::time::{interval, Duration};
#[tokio::main]
async fn main() {
let mut ticker = interval(Duration::from_secs(1));
for i in 1..=5 {
ticker.tick().await; // 最初のtickは即座に返る
println!("{}秒経過: {}回目の処理", i - 1, i);
}
}
注意:
intervalの最初のtick()は即座に返ります。これは「インターバル開始時点」をゼロ秒とするためです。
実用的な例:定期的にステータスを報告する
use tokio::time::{interval, Duration};
async fn check_status(id: u32) {
println!("サービス {} のステータスチェック完了", id);
}
#[tokio::main]
async fn main() {
let mut ticker = interval(Duration::from_secs(3));
let mut count = 0u32;
loop {
ticker.tick().await;
count += 1;
check_status(count).await;
if count >= 4 {
println!("監視終了");
break;
}
}
}
この仕組みを使うと、ヘルスチェックやキャッシュの定期更新といった処理を簡潔に書けます。
tokio::time::timeout でタイムアウト処理
基本的な使い方
外部APIへのリクエストなど、完了までの時間が不確かな処理にはタイムアウトが欠かせません。
use tokio::time::{timeout, sleep, Duration};
async fn slow_task() -> String {
sleep(Duration::from_secs(5)).await;
"完了".to_string()
}
#[tokio::main]
async fn main() {
match timeout(Duration::from_secs(2), slow_task()).await {
Ok(result) => println!("成功: {}", result),
Err(_) => println!("タイムアウト!2秒以内に完了しませんでした"),
}
}
timeout は Result<T, Elapsed> を返します。タイムアウトした場合は Err(Elapsed) になります。
タイムアウト付きのリトライ処理
use tokio::time::{timeout, sleep, Duration};
async fn fetch_data() -> Result<String, &'static str> {
// 模擬的に時間のかかる処理
sleep(Duration::from_millis(300)).await;
Ok("データ取得成功".to_string())
}
#[tokio::main]
async fn main() {
for attempt in 1..=3 {
println!("試行 {} 回目", attempt);
match timeout(Duration::from_millis(500), fetch_data()).await {
Ok(Ok(data)) => {
println!("成功: {}", data);
return;
}
Ok(Err(e)) => println!("エラー: {}", e),
Err(_) => println!("タイムアウト"),
}
sleep(Duration::from_millis(100)).await;
}
println!("全試行失敗");
}
sleep・interval・timeout の使い分けまとめ
| 関数 | 用途 |
|---|---|
sleep | 一定時間待ってから次の処理へ進む |
interval | 定期的に処理を繰り返す |
timeout | 指定時間内に完了しなければエラーにする |
まとめ
Tokioの time モジュールを使うと、非同期環境でのタイマー処理を直感的に書けます。
sleepでスレッドをブロックせずに待機intervalで定期実行ループを簡潔に実装timeoutで時間制限付きの処理を安全に扱う
これらを組み合わせることで、実用的な非同期アプリケーションを構築できます。tokio::select! と組み合わせるとさらに柔軟な制御が可能になるので、ぜひ挑戦してみてください。