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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 01:39:57