You need to enable JavaScript to run this app. Blazor应用绑定StateHasChanged、异步while循环后多次点击Start按钮卡顿问题
优惠活动
大模型
产品
解决方案
定价
更多

Blazor应用绑定StateHasChanged、异步while循环后多次点击Start按钮卡顿问题

问题根因
  • 结论明确:多次点击Start按钮确实会创建多个并行运行的无限while循环,这是卡顿现象的核心诱因。
  • 实际触发链路:
    • 每次调用StartGame时,方法内没有任何停止原有循环的逻辑,直接无判断调用MainLoop
    • MainLoop方法标记为async void,调用后不会等待之前的循环执行完成,会直接开启新的执行流
    • 原有循环的IsRunning标志始终为true,不会被任何逻辑修改,会持续在后台以20ms为间隔触发MainLoopCompleted事件
    • N次点击后就会存在N个并行的循环,每20ms触发N次StateHasChanged,UI渲染压力随点击次数线性增长,最终造成严重卡顿
现有运行标记的缺陷

代码里定义的IsRunning标志目前无法实现循环互斥,核心问题有两个:

  • 每次进入新的MainLoop时,第一行就把IsRunning设为true,从来没有在启动新循环前将该标志置为false
  • 新循环启动时不会等待旧循环完成退出,旧循环的while判断条件始终满足,会持续运行

实际运行的流程可以直观体现问题:

  1. 第一次点击Start:IsRunning = true,循环A启动,每20ms触发一次UI更新
  2. 第二次点击Start:重置游戏状态,直接调用MainLoop,IsRunning再次被设为true(状态无变化),循环B启动
  3. 此时循环A、B并行,每20ms触发2次StateHasChanged,卡顿程度翻倍
  4. 后续每多点击一次,就多一个并行循环,卡顿持续加剧
修复实现

1. 在启动新循环前终止旧循环

修改StartGame方法逻辑,在所有状态初始化前先将运行标志置为false,让旧循环自然退出:

public void StartGame()
{
    // 终止所有正在运行的旧循环
    IsRunning = false;

    // 原有游戏状态初始化逻辑
    _mainSquare = new MainSquare();
    _otherSquares = new List<OtherSquares>();
    _colorSquare = new SqrBoard();
    _monsterClass = new MonsterClass();
    firstlist = new List<OtherSquares>();
    message = "";
    AddSquares();

    // 启动新循环
    MainLoop();
}

2. 调整MainLoop的循环启动时机

修改MainLoop的逻辑,等待旧循环完全退出后再将运行标志置为true,避免并发冲突;同时将async void替换为更规范的async Task返回值,方便异常捕获:

public async Task MainLoop()
{
    // 预留1帧的延迟,确保旧循环已经完成条件判断、退出while块
    await Task.Delay(1);
    IsRunning = true;

    while (IsRunning)
    {
        SomeLoop();
        CalculateSquares();
        MonsterAction();
        MainLoopCompleted?.Invoke(this, EventArgs.Empty);
        await Task.Delay(20);
        GameOver();   
    }
}

3. 优化UI更新触发逻辑

调整Index页面中的事件订阅逻辑,避免在循环停止阶段触发不必要的UI渲染;同时补充组件销毁时的循环终止逻辑,避免资源泄漏。

注意:要实现组件销毁时的循环终止逻辑,需要在Index页面顶部添加@implements IDisposable标记,框架才会在组件从页面移除时自动调用Dispose方法。

@page "/"
@implements IDisposable

@code { 
    GameManager _gameM;
    public string message { get; set; } = "";
    
    protected override void OnInitialized()
    {
        _gameM = new GameManager();
        _gameM.MainLoopCompleted += (e, o) =>
        {
            // 仅在游戏处于运行状态时触发UI更新
            if (_gameM.IsRunning)
            {
                StateHasChanged();
            }
        };
    }

    public void Dispose()
    {
        // 组件销毁时终止循环,避免后台持续占用资源
        _gameM.IsRunning = false;
    }
}

补充说明:原来的async void在长期运行的循环场景中存在异常无法被全局捕获的问题,修改为返回Task后,Blazor框架可以正确捕获循环内抛出的异常,不会造成应用无预兆崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.02 15:36:04