あっぽログ
← 記事一覧に戻る

RustのTokioでタイマーとスリープを使いこなす:time::sleepとIntervalで定期処理を実装する

はじめに

非同期プログラミングでは「一定時間待つ」「定期的に処理を実行する」といった時間ベースの制御が頻繁に必要になります。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秒以内に完了しませんでした"),
    }
}

timeoutResult<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! と組み合わせるとさらに柔軟な制御が可能になるので、ぜひ挑戦してみてください。

← 記事一覧に戻る