Yew框架异步调用MinMax算法解决页面卡顿问题求助
问题描述
使用Yew框架开发桌游前端应用时,同步调用MinMax算法寻找最优走法会导致页面冻结。参考Yew Futures示例实现异步调用后,调用API的Markdown请求能正常显示“Fetching”加载提示并异步获取数据,但本地AI算法的异步调用仍会冻结页面。日志显示已触发Fetching状态,但ai_async_choose_piece_for_opponent()函数中的JsFuture::from(ai_promise).await?仍表现为同步执行。需要适配无需调用API的本地异步计算方案。
核心原因
浏览器主线程同时负责UI渲染与JavaScript代码执行,CPU密集型的同步计算会完全阻塞主线程,导致页面无法响应。调用fetch接口时,浏览器会在后台发起网络请求,主线程不会被阻塞;但本地MinMax计算是同步执行在主线程,即便用future_to_promise包装,也只是将同步任务封装为Promise,并未脱离主线程执行,因此仍会阻塞页面。
解决方案
方案1:使用Web Worker隔离CPU密集型计算(推荐)
将MinMax算法放到Web Worker中执行,彻底与主线程隔离,避免阻塞UI渲染。
步骤1:创建Web Worker脚本
编写独立的Worker代码,负责执行AI计算:
use gloo_workers::prelude::*; use quarto_game::{ai::Strategy, board::Board, piece::Piece}; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] struct WorkerRequest; #[derive(Serialize, Deserialize)] struct WorkerResponse { piece: String, } #[worker] fn ai_worker(req: WorkerRequest) -> Result<WorkerResponse, ()> { // 在Worker线程中执行AI计算 let board = Board::create(); let mut minmax = quarto_game::ai::minmax_tree::MinMaxTree::new(2, true); let p = Strategy::choose_piece_for_opponent(&mut minmax, &board); Ok(WorkerResponse { piece: p.as_text().to_string(), }) }
步骤2:修改Yew组件中的异步函数
在组件中与Worker通信,发送计算请求并接收结果:
use gloo_workers::Worker; use serde::{Deserialize, Serialize}; use quarto_game::piece::Piece; #[derive(Serialize, Deserialize)] struct WorkerRequest; #[derive(Serialize, Deserialize)] struct WorkerResponse { piece: String, } async fn ai_async_choose_piece_for_opponent() -> Result<Piece, FetchError> { // 初始化Worker,路径为编译后的Worker脚本 let worker = Worker::new("./ai_worker.js")?; // 发送请求并等待结果 let resp = worker.send(WorkerRequest).await?; Ok(Piece::from(resp.piece.as_str())) }
步骤3:调整组件更新逻辑
确保先触发加载状态更新,再启动异步任务:
impl Component for App { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { match msg { Msg::SetDataFetchState(fetch_state) => { self.markdown = fetch_state; true } Msg::GetData(data_type) => { // 先发送加载状态,让UI立即更新 ctx.link().send_message(Msg::SetDataFetchState(FetchState::Fetching)); // 启动异步任务 match data_type { DataType::Markdown => { ctx.link().send_future(async { match fetch_markdown(MARKDOWN_URL).await { Ok(md) => Msg::SetDataFetchState(FetchState::Success(md)), Err(err) => Msg::SetDataFetchState(FetchState::Failed(err)), } }); }, DataType::AI => { ctx.link().send_future(async { match ai_async_choose_piece_for_opponent().await { Ok(p) => Msg::SetDataFetchState(FetchState::Success(format!("Piece selected : {}", p.as_text()))), Err(err) => Msg::SetDataFetchState(FetchState::Failed(err)), } }); } }; false } } } // ... 其余代码保持不变 }
方案2:使用延迟计算临时解决(适合小计算量场景)
如果不想引入Web Worker,可以用gloo_timers将计算延迟到下一个事件循环,让主线程先完成UI更新。
步骤1:添加依赖
在Cargo.toml中添加:
gloo-timers = "0.2"
步骤2:修改AI异步函数
use gloo_timers::future::TimeoutFuture; use quarto_game::{ai::Strategy, board::Board, piece::Piece}; async fn ai_async_choose_piece_for_opponent() -> Result<Piece, FetchError> { // 延迟1ms,让主线程先更新UI TimeoutFuture::new(1).await; // 执行AI计算 let board = Board::create(); let mut minmax = quarto_game::ai::minmax_tree::MinMaxTree::new(2, true); let p = Strategy::choose_piece_for_opponent(&mut minmax, &board); Ok(p) }
步骤3:调整组件更新逻辑
同方案1,先发送加载状态消息,再启动异步任务。
方案对比
- Web Worker方案:彻底隔离计算任务,主线程完全不阻塞,适合复杂、耗时的AI计算,是生产环境推荐方案。
- 延迟计算方案:实现简单,但计算仍在主线程执行,计算过程中页面仍会冻结,仅适合计算量较小的场景。
内容的提问来源于stack exchange,提问作者Apolixit
相关产品推荐
相关产品推荐

