ネットワークリクエストやファイルI/Oなど、時間のかかる処理をブロッキングせずに行うために async コマンドを使用します。
Tauri コマンドは async fn をそのままサポートしています。
なぜ async が必要なのか?
Rust の通常の関数(async なし)は、処理が完了するまでスレッドを占有します。
もしUIスレッド(メインスレッド)で数秒かかる重い処理を実行してしまうと、その間アプリの画面がフリーズしてしまいます。
async 関数を使うと、処理の待ち時間にスレッドを開放できるため、裏で重い処理をしつつも、アプリの操作性を損なわないようにできます。
前提条件
1. クレート (Tokio) の追加
Tauri は内部で tokio ランタイムを使用していますが、自分で非同期関数(sleepなど)を使うには、プロジェクトにも明示的に tokio を追加する必要があります。
src-tauri ディレクトリで以下のコマンドを実行してください。
cd src-tauri
cargo add tokio --features full
2. Permissions (権限) の設定
src-tauri/capabilities/default.json に以下の権限を追加します。
{
"permissions": [
...
]
}
実装例
std::thread::sleep ではなく、非同期対応の tokio::time::sleep を使うのがポイントです。
use std::time::Duration;
use tokio::time::sleep;
// async fn をつけるだけで非同期コマンドになります
#[tauri::command]
async fn fetch_remote_data(url: String) -> Result<String, String> {
// ログ出力など
println!("Fetching: {}", url);
// ここで重い処理(待機)のシミュレーション
// await をつけることで、この待機時間は他の処理(UI描画など)にスレッドを譲ります
sleep(Duration::from_secs(2)).await;
Ok(format!("Data from {}", url))
}
並列実行上の注意
Tauri の非同期コマンドは、Rust の代表的な非同期ランタイムである Tokio 上で実行されます。
NG な例: async 内でのブロッキング処理
async コマンドの中で、ブロッキング処理(スレッドを占有して止まる処理)を行うのは避けてください。
例えば、std::thread::sleep や、大量の計算ループなどを async 関数内にそのまま書くと、Tokio の実行スレッド全体が止まってしまい、他の非同期タスクも遅延します。
// 悪い例
#[tauri::command]
async fn bad_command() {
// これは UI はフリーズしませんが(別スレッドプールのため)、
// 他の非同期コマンドの実行を阻害する可能性があります。
std::thread::sleep(Duration::from_secs(5));
}
解決策: spawn_blocking
CPUを大量に使う計算や、非同期に対応していない古いライブラリを使う場合は、spawn_blocking でラップして実行します。
#[tauri::command]
async fn heavy_computation() -> Result<String, String> {
let result = tokio::task::spawn_blocking(|| {
// ここでの処理は専用のスレッドプールで実行されます
// 重い計算や std::thread::sleep もOK
std::thread::sleep(Duration::from_secs(2));
"Done"
}).await.map_err(|e| e.to_string())?;
Ok(result.to_string())
}
フロントエンドからの呼び出し
フロントエンド(JavaScript/TypeScript)からは、通常のコマンドと同じように呼び出せます。
Rust 側が async かどうかに関わらず、invoke は常に Promise を返します。
import { invoke } from '@tauri-apps/api/core';
async function loadData() {
try {
console.log('Fetching data...');
// invoke は非同期関数なので await で待機します
// Rust 側の処理が終わるまでここで止まります(UIは止まりません)
const result = await invoke('fetch_remote_data', { url: 'https://example.com' });
console.log(result); // "Data from https://example.com"
} catch (error) {
console.error('Failed to fetch:', error);
}
}