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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 17:55:46