如何在Tauri中运行线程?解决目录同步UI冻结问题
解决方案
问题根源
Tauri的默认命令运行在主线程中,你的sync_directory函数调用了process_directory,其中包含无限loop,会持续阻塞主线程,直接导致UI冻结。另外,循环内重复创建Tokio Runtime也是不必要的性能浪费。
具体修复步骤
1. 改造Tauri命令为异步并启动后台任务
将sync_directory改为异步命令,利用Tokio的spawn把目录监听逻辑放到后台线程执行,让主线程能立即返回结果,不阻塞UI渲染。
修改后的Rust命令代码:
use tokio::spawn; use std::path::Path; use notify::{Watcher, RecursiveMode, DebouncedEvent}; use std::time::Duration; #[tauri::command] async fn sync_directory(directory: String, filter: String, bucket: char) -> SyncStatus { let target_path = Path::new(&directory); println!("Watching {}", target_path.display()); if !target_path.exists() { return SyncStatus { directory: "".to_string(), filter: "".to_string(), status: false, }; } // 复制参数供后台线程捕获 let filter_clone = filter.clone(); let bucket_clone = bucket; // 启动后台任务执行监听逻辑 spawn(async move { let (sender, receiver) = std::sync::mpsc::channel(); let mut watcher = notify::watcher(sender, Duration::from_secs(1)).unwrap(); if let Err(e) = watcher.watch(&directory, RecursiveMode::Recursive) { eprintln!("Failed to watch directory: {:?}", e); return; } // 处理监听事件 process_directory(receiver, &filter_clone, bucket_clone); // watcher会在此异步块内保持存活,不会提前被销毁 }); SyncStatus { directory, filter, status: true, } }
2. 优化事件处理的Runtime使用
不在循环内重复创建Tokio Runtime,全局初始化一次复用,同时调整上传逻辑的异步执行方式:
use lazy_static::lazy_static; // 全局初始化Tokio Runtime,避免重复创建 lazy_static! { static ref RUNTIME: tokio::runtime::Runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); } fn process_directory(receiver: std::sync::mpsc::Receiver<DebouncedEvent>, filter: &str, bucket: char) { loop { match receiver.recv() { Ok(event) => { let current_time = chrono::offset::Local::now().format("%Y-%m-%d %H:%M:%S"); match event { notify::DebouncedEvent::Create(file_path) => { let file_properties = get_file_properties(file_path); println!("{} Create: {}", current_time, file_properties.name); if file_properties.ext == filter && bucket as u32 >= 65 && bucket as u32 <= 90 { let key = format!("{}_{}", get_current_timestamp(), file_properties.name); println!("Inside if"); // 使用全局Runtime执行异步上传 let _ = RUNTIME.spawn(async move { if let Err(e) = upload_to_s3(file_properties.path, key, bucket).await { eprintln!("Upload failed: {:?}", e); } }); } } notify::DebouncedEvent::Write(file_path) => { println!("{} Write: {}", current_time, file_path.display()); } notify::DebouncedEvent::Chmod(file_path) => { println!("{} Chmod: {}", current_time, file_path.display()); } notify::DebouncedEvent::Remove(file_path) => { println!("{} Remove: {}", current_time, file_path.display()); } notify::DebouncedEvent::Rename(file_path, _) => { println!("{} Rename: {}", current_time, file_path.display()); } notify::DebouncedEvent::Error(err, Some(file_path)) => { println!("{} Error: {}, {}", current_time, err, file_path.display()); } _other => {} } } Err(e) => { eprintln!("watch error: {:?}", e); break; // 出错时退出循环,避免无限报错 } } } }
3. 前端添加状态提示(可选)
为提升用户体验,可以在SolidJS中添加同步状态的显示:
let isSyncing = false; async function createDirectorySync() { try { isSyncing = true; let result = await invoke("sync_directory", { directory: directory(), filter: filter(), bucket: bucket() }); console.log(result); } catch (err) { console.error("Sync failed:", err); } finally { isSyncing = false; } }
按钮部分可以根据状态禁用或显示加载中:
<button type="button" class="sync-btn" onClick={() => createDirectorySync()} disabled={isSyncing} > {isSyncing ? "Syncing..." : "Sync"} </button>
核心注意事项
- 主线程不阻塞:所有长时间运行或无限循环的逻辑必须放到后台线程,Tauri主线程仅负责UI渲染。
- 资源复用:全局初始化Tokio Runtime,避免循环内重复创建带来的性能损耗。
- 生命周期管理:确保Watcher在后台任务中保持存活,不会提前被销毁导致监听失效。
内容的提问来源于stack exchange,提问作者snipedown21
相关产品推荐
相关产品推荐

