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

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))
}

关键说明

  1. spawn_blocking的作用:将网页解析、文件IO这类同步阻塞操作放到专门线程池执行,既不阻塞Tauri的异步运行时,又能保证整个Future满足Send要求。
  2. 变量克隆:由于spawn_blocking会获取变量所有权,需要提前克隆所有要传递给get_table的变量,确保它们能安全转移到阻塞线程中。
  3. 错误处理:添加了对线程调度和任务执行的错误捕获,避免程序panic,你可以根据需求调整错误处理逻辑。

内容的提问来源于stack exchange,提问作者Hashir Shahzad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 12:04:52