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

如何从JavaScript取消Rust编写的Wasm长耗时函数?

优化Wasm长耗时函数的UI取消方案

针对你提出的问题,这里提供几个比setTimeout(0)更优雅、高效的实现方案:

方案1:用queueMicrotask替代setTimeout(0)

queueMicrotask是浏览器原生提供的轻量事件让出API,它会将回调加入微任务队列,比setTimeout(0)的宏任务开销更小,响应更及时,能更精准地让浏览器处理完当前待执行的UI事件。

修改handle_js_events函数如下:

use js_sys::{Function, Promise};
use wasm_bindgen_futures::JsFuture;

#[wasm_bindgen]
extern "C" {
    fn queueMicrotask(closure: &Function);
}

async fn handle_js_events() {
    JsFuture::from(Promise::new(&mut |resolve: Function, _reject: Function| {
        queueMicrotask(&resolve);
    }))
    .await
    .unwrap();
}

方案2:基于Promise的被动取消(无需轮询)

当前轮询检查取消状态的方式可以优化为被动监听取消信号,避免不必要的循环检查。调整JsCancellationToken的实现,让它在取消时主动触发Promise完成:

use js_sys::{Function, Promise};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use std::cell::RefCell;

#[wasm_bindgen]
pub struct JsCancellationToken {
    cancel_promise: Promise,
    resolve_cancel: RefCell<Option<Function>>,
}

#[wasm_bindgen]
impl JsCancellationToken {
    pub fn new() -> JsCancellationToken {
        let mut resolve_cancel = RefCell::new(None);
        let cancel_promise = Promise::new(&mut |resolve: Function, _reject: Function| {
            *resolve_cancel.borrow_mut() = Some(resolve);
        });

        JsCancellationToken {
            cancel_promise,
            resolve_cancel,
        }
    }

    pub fn cancel(&self) {
        if let Some(resolve) = self.resolve_cancel.borrow_mut().take() {
            resolve.call0(&JsValue::undefined()).unwrap();
        }
    }

    pub fn cancellation_future(&self) -> JsFuture {
        JsFuture::from(self.cancel_promise.clone())
    }
}

修改长耗时函数,用futures::future::race同时执行任务逻辑和取消信号:

use futures::future::{self, Either};

#[wasm_bindgen]
pub async fn long_running_function(ct: &JsCancellationToken) -> u32 {
    // 定义分步执行的任务逻辑
    let task = async {
        let mut counter = 0;
        loop {
            // 执行单次任务步骤
            counter += 1;
            
            // 每步后让出微任务队列,保证UI事件能被处理
            JsFuture::from(Promise::new(&mut |resolve, _| {
                queueMicrotask(&resolve);
            })).await.unwrap();
            
            // 任务完成条件
            if counter >= 1000 {
                break counter;
            }
        }
    };

    match future::race(task, ct.cancellation_future()).await {
        Either::Left(result) => result, // 任务正常完成
        Either::Right(_) => 0, // 取消触发,返回取消标识
    }
}

这个方案的核心是取消信号由UI事件主动触发,无需轮询检查,性能和代码优雅度都更优。

方案3:用AtomicBool实现高性能状态检查

如果你的函数需要极致性能,且可以接受周期性主动让出事件循环,可改用AtomicBool替代Cell<bool>,取消状态检查无需await,只在关键步骤主动让出事件循环:

use std::sync::atomic::{AtomicBool, Ordering};
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct JsCancellationToken {
    token: AtomicBool,
}

#[wasm_bindgen]
impl JsCancellationToken {
    pub fn new() -> JsCancellationToken {
        JsCancellationToken {
            token: AtomicBool::new(false),
        }
    }

    pub fn cancel(&self) {
        self.token.store(true, Ordering::SeqCst);
    }

    pub fn is_cancellation_requested(&self) -> bool {
        self.token.load(Ordering::SeqCst)
    }
}

#[wasm_bindgen]
pub async fn long_running_function(ct: &JsCancellationToken) -> u32 {
    let mut counter = 0;
    while !ct.is_cancellation_requested() {
        counter += 1;
        // 每执行N步让出一次事件循环,避免长时间阻塞
        if counter % 100 == 0 {
            handle_js_events().await;
        }
    }
    counter
}

这里的handle_js_events仍使用queueMicrotask实现,兼顾性能和事件循环的响应性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 23:40:26