如何在Tauri中切换页面时终止invoke调用的Promise?
解决Tauri页面切换时终止后台invoke请求的问题
Tauri的invoke本身没有内置的终止机制,要实现页面切换时停止后台任务,需要前端+Rust端配合搭建可取消的任务流,具体方案如下:
1. Rust端实现可取消的命令
借助tokio::sync::CancellationToken追踪任务状态,通过全局线程安全存储关联任务ID与取消令牌,支持主动终止任务:
use std::collections::HashMap; use std::sync::{Arc, Mutex}; use tokio::sync::CancellationToken; // 全局存储任务ID与取消令牌的映射,保证多线程安全 lazy_static::lazy_static! { static ref TASK_TOKENS: Arc<Mutex<HashMap<String, CancellationToken>>> = Arc::new(Mutex::new(HashMap::new())); } #[tauri::command] async fn long_running_task(task_id: String) -> Result<String, String> { let token = CancellationToken::new(); // 将令牌存入全局映射 TASK_TOKENS.lock().unwrap().insert(task_id.clone(), token.clone()); // 模拟耗时任务,必须定期检查取消信号 for i in 0..10 { if token.is_cancelled() { TASK_TOKENS.lock().unwrap().remove(&task_id); return Err("任务已取消".into()); } tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; } // 任务完成后清理令牌 TASK_TOKENS.lock().unwrap().remove(&task_id); Ok("任务完成".into()) } #[tauri::command] async fn cancel_task(task_id: String) -> Result<(), String> { let mut tokens = TASK_TOKENS.lock().unwrap(); if let Some(token) = tokens.get(&task_id) { token.cancel(); Ok(()) } else { Err("任务不存在或已完成".into()) } }
注意:如果是CPU密集型的同步任务,需要用
tokio::task::spawn_blocking包装,并在任务内部定期检查取消令牌,否则无法响应终止信号。
2. 前端绑定页面生命周期,触发任务取消
前端调用任务时生成唯一task_id,在页面/组件卸载时调用cancel_task命令终止对应任务:
Vue3示例
import { invoke } from '@tauri-apps/api/tauri' import { v4 as uuidv4 } from 'uuid' import { onUnmounted } from 'vue' export default { setup() { let currentTaskId = null const startLongTask = async () => { currentTaskId = uuidv4() try { const result = await invoke('long_running_task', { taskId: currentTaskId }) console.log('任务结果:', result) } catch (err) { // 仅处理非取消类错误 if (err !== '任务已取消') { console.error('任务出错:', err) } } } // 页面卸载时取消任务 onUnmounted(() => { if (currentTaskId) { invoke('cancel_task', { taskId: currentTaskId }).catch(() => {}) } }) return { startLongTask } } }
React示例
import { invoke } from '@tauri-apps/api/tauri' import { v4 as uuidv4 } from 'uuid' import { useEffect, useState } from 'react' export const TaskComponent = () => { const [currentTaskId, setCurrentTaskId] = useState(null) const startLongTask = async () => { const taskId = uuidv4() setCurrentTaskId(taskId) try { const result = await invoke('long_running_task', { taskId }) console.log('任务结果:', result) } catch (err) { if (err !== '任务已取消') { console.error('任务出错:', err) } } finally { setCurrentTaskId(null) } } // 组件卸载时取消任务 useEffect(() => { return () => { if (currentTaskId) { invoke('cancel_task', { taskId: currentTaskId }).catch(() => {}) } } }, [currentTaskId]) return <button onClick={startLongTask}>启动耗时任务</button> }
原生JS示例
import { invoke } from '@tauri-apps/api/tauri' import { v4 as uuidv4 } from 'uuid' let currentTaskId = null const startLongTask = async () => { currentTaskId = uuidv4() try { const result = await invoke('long_running_task', { taskId: currentTaskId }) console.log('任务结果:', result) } catch (err) { if (err !== '任务已取消') { console.error('任务出错:', err) } } } // 页面卸载时触发取消 window.addEventListener('beforeunload', () => { if (currentTaskId) { invoke('cancel_task', { taskId: currentTaskId }).catch(() => {}) } })
3. 额外注意事项
- 任务ID需保证全局唯一,推荐用UUID生成,避免冲突。
- 取消任务的
invoke调用可忽略错误(比如任务已完成),无需额外处理。 - 若存在多个并行任务,需存储多个
taskId,在页面卸载时批量取消。
内容的提问来源于stack exchange,提问作者Neku80
相关产品推荐
相关产品推荐

