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判断条件始终满足,会持续运行
实际运行的流程可以直观体现问题:
- 第一次点击Start:
IsRunning = true,循环A启动,每20ms触发一次UI更新 - 第二次点击Start:重置游戏状态,直接调用
MainLoop,IsRunning再次被设为true(状态无变化),循环B启动 - 此时循环A、B并行,每20ms触发2次
StateHasChanged,卡顿程度翻倍 - 后续每多点击一次,就多一个并行循环,卡顿持续加剧
修复实现
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
相关产品推荐
相关产品推荐

