Tauri命令中异步函数await报错:Future无法安全跨线程发送
Tauri异步命令中使用await时Send trait兼容问题
我是Rust新手,对Future等概念了解有限。此前程序在终端运行正常,现使用Tauri开发UI界面,却遇到无法在tauri::command标记的异步函数中调用await的问题。
相关代码
Main.rs
#[tauri::command(rename_all = "snake_case")] async fn url_entered(url: &str, img_box_checked:bool, pdf_box_checked:bool, subfolder_box_checked:bool) -> Result<String, ()> { println!("IMAGE:{} | PDF:{} | Folder:{} | URL: {}", img_box_checked, pdf_box_checked, subfolder_box_checked, url); let _ = get_table(url, CURRENT_DIRECTORY, pdf_box_checked, img_box_checked, subfolder_box_checked).await; // 此处await触发错误 Ok(format!("Sending request to: {}", url)) }
Main.js
const { invoke } = window.__TAURI__.tauri; let urlInputEl; let urlMsgEl; let downloadPdfs; let downloadImages; let scanSubfolders; async function url_submitted() { urlMsgEl.textContent = "Trying to download..."; const { url, img_box_checked, pdf_box_checked, subfolder_box_checked } = { url: urlInputEl.value, img_box_checked: downloadImages.checked, pdf_box_checked: downloadPdfs.checked, subfolder_box_checked: scanSubfolders.checked }; urlMsgEl.textContent = await invoke("url_entered", { url, img_box_checked, pdf_box_checked, subfolder_box_checked }).then(() => console.log('Completed!')); } window.addEventListener("DOMContentLoaded", () => { urlInputEl = document.querySelector("#url-input"); urlMsgEl = document.querySelector("#url-msg"); downloadPdfs = document.querySelector("#DownloadPdfs"); downloadImages = document.querySelector("#DownloadImages"); scanSubfolders = document.querySelector("#ScanSubfolders"); document.querySelector("#url-form").addEventListener("submit", (e) => { e.preventDefault(); url_submitted(); }); });
HTML按钮
<button type="submit">Enter</button>
错误日志
error: future cannot be sent between threads safely --> src\main.rs:214:1 | 214 | #[tauri::command(rename_all = "snake_case")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `url_entered` is not `Send` ... 231 | .invoke_handler(tauri::generate_handler![url_entered]) | ------------------------------------- in this macro invocation | = help: within `ego_tree::Node<scraper::Node>`, the trait `Sync` is not implemented for `std::cell::Cell<NonZero<usize>>`, which is required by `impl futures::Future<Output = Result<String, ()>>: std::marker::Send` = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: future is not `Send` as this value is used across an await --> src\main.rs:89:90 | 35 | for table in html.select(&table_selector){ // get all tables from html | ----- has type `ElementRef<'_>` which is not `Send` ... 89 | download_pdfs, download_imgs, scan_subfolders)).await?; // Call get_table function with new url | ^^^^^ await occurs here, with `table` maybe used later note: required by a bound in `ResultFutureTag::future` --> C:\Users\DELL\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tauri-1.6.8\src\command.rs:293:42 | 289 | pub fn future<T, E, F>(self, value: F) -> impl Future<Output = Result<Value, InvokeError>> | ------ required by a bound in this associated function ... 293 | F: Future<Output = Result<T, E>> + Send, | ^^^^ required by this bound in `ResultFutureTag::future` = note: this error originates in the macro `__cmd__url_entered` which comes from the expansion of the macro `tauri::generate_handler` (in Nightly builds, run with -Z macro-backtrace for more info)
解决方案
问题根源是get_table函数内部使用了scraper库的ElementRef类型,该类型包含std::cell::Cell,不满足Send trait要求。而Tauri的命令系统要求异步函数返回的Future必须是Send的,因为Tauri会在不同线程间调度命令任务。
解决方法是将同步阻塞的get_table调用放到tokio::task::spawn_blocking中执行,让它在专门的阻塞线程池中运行,避免非Send变量跨await传递:
#[tauri::command(rename_all = "snake_case")] async fn url_entered(url: &str, img_box_checked:bool, pdf_box_checked:bool, subfolder_box_checked:bool) -> Result<String, ()> { println!("IMAGE:{} | PDF:{} | Folder:{} | URL: {}", img_box_checked, pdf_box_checked, subfolder_box_checked, url); // 克隆需要传递的变量,因为spawn_blocking会转移所有权 let url_clone = url.to_string(); let current_dir = CURRENT_DIRECTORY.clone(); // 假设CURRENT_DIRECTORY是可Clone的类型,比如String let pdf_flag = pdf_box_checked; let img_flag = img_box_checked; let subfolder_flag = subfolder_box_checked; // 用spawn_blocking运行同步任务 let result = tokio::task::spawn_blocking(move || { get_table(&url_clone, current_dir, pdf_flag, img_flag, subfolder_flag) }).await; // 处理任务执行结果 match result { Ok(inner_result) => { if let Err(e) = inner_result { eprintln!("下载任务执行失败: {:?}", e); } } Err(e) => { eprintln!("线程调度失败: {:?}", e); } } Ok(format!("请求已发送至: {}", url)) }
关键说明
spawn_blocking的作用:将网页解析、文件IO这类同步阻塞操作放到专门线程池执行,既不阻塞Tauri的异步运行时,又能保证整个Future满足Send要求。- 变量克隆:由于
spawn_blocking会获取变量所有权,需要提前克隆所有要传递给get_table的变量,确保它们能安全转移到阻塞线程中。 - 错误处理:添加了对线程调度和任务执行的错误捕获,避免程序panic,你可以根据需求调整错误处理逻辑。
内容的提问来源于stack exchange,提问作者Hashir Shahzad
相关产品推荐
相关产品推荐

