重い計算処理を非同期で行う

Recipe ID: perf-009

フロントエンドからのリクエストに対して、Rust 側で時間のかかる処理を行う場合、UI をフリーズさせないために非同期コマンド (async fn) として実装し、適切なスレッド戦略を取る必要があります。

1. アンチパターン (UIフリーズの原因)

以下のコードは非同期関数ですが、重い処理が Tokio のワーカースレッドを占有してしまうため、他のイベント処理が遅延する可能性があります。

// 良くない例: CPUバウンドな処理を直接実行
#[tauri::command]
async fn heavy_task() {
    let mut sum = 0;
    for i in 0..10_000_000_000 { // 長いループ
        sum += i;
    }
}

2. 推奨パターン: spawn_blocking

CPU バウンドな処理(計算、画像処理、暗号化など)は spawn_blocking で専用のスレッドプールに逃がします。

#[tauri::command]
async fn heavy_task_optimized() -> Result<u64, String> {
    let result = tauri::async_runtime::spawn_blocking(move || {
        // 重い処理をここで行う
        let mut sum: u64 = 0;
        for i in 0..1_000_000_000 {
            sum += i;
        }
        sum
    })
    .await
    .map_err(|e| e.to_string())?;

    Ok(result)
}

3. 進捗状況の通知 (Progress Reporting)

処理時間が長い場合、ユーザーに進捗を伝えることが UX 上重要です。Window または AppHandle を使用してイベントを発行します。

use tauri::Emitter;

#[tauri::command]
async fn task_with_progress(window: tauri::Window) -> Result<(), String> {
    tauri::async_runtime::spawn_blocking(move || {
        for i in 0..100 {
            // 処理...
            std::thread::sleep(std::time::Duration::from_millis(50));
            
            // 進捗イベント発行
            let _ = window.emit("progress", i);
        }
    }).await.map_err(|e| e.to_string())?;

    Ok(())
}