Tauri(Rust)のメインスレッドは UI の更新なども担当しているため、ここで重い処理(数秒かかる計算など)を行うとアプリがフリーズしたように固まってしまいます。
これを防ぐために、重い処理は「別スレッド」で実行するのが鉄則です。
Cargo.toml の確認 (Rust)
async コマンド内で tokio の機能(tokio::time::sleep など)を直接使うには、Cargo.toml に tokio を追加する必要があります。
[dependencies]
tauri = { version = "2.2", features = [] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# 以下を追加 (full機能を含めると便利です)
tokio = { version = "1", features = ["full"] }
1. 計算メインの処理 (std::thread)
CPU をぶん回して計算するような処理は、Rust 標準の std::thread::spawn が適しています。
use tauri::{AppHandle, Emitter};
#[tauri::command]
fn start_heavy_calc(app: AppHandle) {
// スレッド起動(ここから別世界の処理になります)
std::thread::spawn(move || {
println!("重い計算を開始...");
// --- 重い処理のシミュレーション ---
// 実際には画像処理や暗号化計算など
std::thread::sleep(std::time::Duration::from_secs(5));
let result = 42;
// 計算が終わったら、イベントなどでフロントエンドに通知する
// AppHandle はスレッドセーフなので move して持ち込んでOK
let _ = app.emit("calc-finished", result);
println!("計算完了!");
});
// spawn した瞬間にこの関数自体は終了し、UIスレッドを開放します
}
2. I/O 待ちメインの処理 (async コマンド)
ファイル読み書きやネットワーク通信など、I/O 待ちは async コマンドとして定義するのがベストプラクティスです。
Rust 側で async fn として定義されたコマンドは、Tauri が自動的に非同期ランタイム(Tokio)上で実行するため、メインスレッド(UI)をブロックしません。
// async fn にするだけで、await 中はスレッドが解放されます
#[tauri::command]
async fn fetch_user_data(user_id: u32) -> Result<String, String> {
println!("ユーザー {} のデータ取得を開始...", user_id);
// I/O待ちのシミュレーション(3秒非同期スリープ)
// 注意: std::thread::sleep ではなく tokio::time::sleep を使います
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
// 実際の HTTP リクエスト例 (reqwestクレートが必要)
// let resp = reqwest::get(format!("https://api.example.com/users/{}", user_id))
// .await
// .map_err(|e| e.to_string())?;
println!("取得完了");
Ok(format!("User {} data", user_id))
}
フロントエンドからは通常のコマンド同様 invoke で呼び出せます。戻り値は Promise となるため await で待機できます。
どちらを使えばいい?
std::thread: 「CPUを常に100%使う」ようなハードな計算処理。async_runtime(Tokio): 「ネットワーク通信」や「タイマー待ち」など、待機時間が長い処理、またはasync関数を使いたい場合。
初心者の方は、まずは「重い処理は fn の中で直接やらずに、spawn で逃がす」と覚えておけばOKです。