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

Blazor WASM多线程实现求助:在线游戏SignalR架构优化

Blazor WASM 单线程下实现权威服务器架构的多线程需求解决方案

问题背景

我用Blazor WASM开发一款简易在线游戏,目前被Blazor客户端的单线程限制卡了脖子——多数游戏算法的执行严重受阻。我的目标是实现权威服务器架构,同时保留客户端代码运行能力(用于延迟补偿等功能)。

既定通信流程

每帧的在线通信逻辑已经明确:

  • 客户端连接后,接收完整游戏状态信息(这部分没问题)
  • 客户端触发操作(移动、射击等)时,向服务器发送操作指令
  • 服务器每帧收集所有客户端操作,同步给所有在线客户端
  • 客户端处理服务器下发的全量操作
  • 客户端与服务器同步执行完整游戏算法

核心矛盾

客户端需要三个独立执行的任务流:

  • 按键触发时向服务器发送操作的任务
  • 接收并处理服务器消息的任务
  • 执行游戏核心算法的任务
    但Blazor WASM的单线程模式根本无法支撑这三者并行,现在用SignalR实现通信,等不及.NET 8正式版(预览版尝试运行失败)。

当前代码示例

帧更新逻辑(每秒60次)

//每秒执行60次
private async Task Frame()
{
    //序列化操作
    json = JsonConvert.SerializeObject(myActions);
    Task.Run(() => hubConnection.SendAsync("ExecuteList", json, id)); //仍为同一线程
    myActions.Clear();
    ToolsGame.ProceedFrame(gvars, now);
    StateHasChanged();  
}

SignalR服务器消息监听

hubConnection.On<string>("ExecuteList", (actionMethodNamesJson) =>
{
        ExecuteList(actionMethodNamesJson); //反序列化所有客户端的操作
        StateHasChanged();
});

可行解决方案建议

1. 用WebWorker分离计算密集型任务

Blazor WASM主线程单线程,但可以借助WebWorker把游戏算法这类耗时逻辑剥离出去,避免阻塞UI和帧更新:

  • 把ToolsGame.ProceedFrame这类计算逻辑迁移到WebWorker中执行,主线程只负责UI渲染和SignalR消息转发
  • 通过IJSInProcessRuntime和WebWorker做数据交互,传递游戏状态和计算结果

示例思路:

// 主线程初始化WebWorker
private async Task InitGameWorker()
{
    await jsRuntime.InvokeVoidAsync("initGameWorker");
    // 注册Worker计算结果回调
    jsRuntime.InvokeVoidAsync("registerFrameResultCallback", DotNetObjectReference.Create(this));
}

// 接收Worker返回的计算结果
[JSInvokable]
public void OnFrameCalculated(GameState newState)
{
    gvars = newState;
    InvokeAsync(StateHasChanged);
}

// 修改帧更新逻辑,把计算交给Worker
private async Task Frame()
{
    json = JsonConvert.SerializeObject(myActions);
    // 异步发送SignalR消息,不阻塞主线程
    await hubConnection.SendAsync("ExecuteList", json, id).ConfigureAwait(false);
    myActions.Clear();
    // 发送当前状态给Worker计算下一帧
    await jsRuntime.InvokeVoidAsync("calculateNextFrame", gvars, now);
    StateHasChanged();
}

2. 优化SignalR调用的异步处理

当前用Task.Run包裹SendAsync没用——Blazor WASM线程池在单线程环境下还是复用主线程。正确做法是:

  • 对SignalR的SendAsync和消息处理逻辑添加ConfigureAwait(false),避免捕获同步上下文,减少主线程阻塞
  • 拆分消息处理流程,把非UI相关的反序列化、操作处理逻辑异步调度,优先保证帧更新执行

修改后的SignalR监听:

hubConnection.On<string>("ExecuteList", async (actionMethodNamesJson) =>
{
    // 异步反序列化,避免阻塞主线程
    var actions = await Task.Run(() => JsonConvert.DeserializeObject<List<GameAction>>(actionMethodNamesJson)).ConfigureAwait(false);
    // 处理操作逻辑
    ExecuteActions(actions);
    // 切回UI线程更新状态
    InvokeAsync(StateHasChanged);
});

3. 帧更新的异步调度优化

用Task.Delay结合ConfigureAwait(false)调度帧循环,避免长时间占用主线程:

private async Task GameLoop()
{
    while (isRunning)
    {
        var now = DateTime.UtcNow;
        await Frame().ConfigureAwait(false);
        // 控制60帧每秒,计算剩余延迟
        var elapsed = DateTime.UtcNow - now;
        var delay = Math.Max(0, 16 - elapsed.Milliseconds);
        await Task.Delay(delay).ConfigureAwait(false);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 18:12:33