You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 13:05:34