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

Blazor Server中SignalR、多线程与UI刷新异常问题排查

Blazor Server中SignalR消息接收后UI无法刷新问题

问题描述

使用Blazor Server开发聊天功能时,从SignalR Hub接收消息后,页面的消息列表UI无法自动刷新。通过按钮添加模拟数据可以正常触发列表刷新,但在SignalR的消息回调方法中调用await InvokeAsync(StateHasChanged)无效。调试发现:按钮添加消息的方法与SignalR回调方法中操作的messages列表内容不一致,怀疑是多线程/非UI线程触发刷新或组件实例问题导致。

关键代码

Razor页面代码

@page "/chat"
@rendermode InteractiveServer
<ul>
    @foreach (var message in messages)
    {
        <li>@message</li>
    }
</ul>

代码后置文件

private readonly List<string> messages = [];
private HubConnection? hubConnection;

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("WHATEVER"), options =>
        {
            options.AccessTokenProvider = async () => Task.FromResult("MYTOKEN");
        })
        .Build();

    hubConnection.On<string, string>("ReceiveMessageAsync", ReceivedNewMessage);

    await hubConnection.StartAsync();
}

private async Task ReceivedNewMessage(string username, string message)
{
    var encodedMsg = $"{username}: {message}";
    messages.Add(encodedMsg);
    await InvokeAsync(StateHasChanged);
}

调试现象

调试确认SignalR回调方法中messages列表已成功添加新消息,但UI绑定的messages列表无新增内容,两个操作的列表实例不一致。

解决方案

1. 将SignalR连接与消息管理移到单例服务

组件实例可能因重新渲染被销毁重建,导致SignalR回调绑定到旧实例的messages列表。将连接与消息管理放到单例服务中,确保所有组件实例共享同一数据源:

步骤1:创建ChatService单例服务

public class ChatService : IDisposable
{
    private readonly List<string> _messages = [];
    private HubConnection? _hubConnection;
    public event Action? OnMessagesUpdated;

    public IEnumerable<string> Messages => _messages.AsReadOnly();

    public async Task Initialize(NavigationManager navigationManager)
    {
        if (_hubConnection != null) return;

        _hubConnection = new HubConnectionBuilder()
            .WithUrl(navigationManager.ToAbsoluteUri("WHATEVER"), options =>
            {
                options.AccessTokenProvider = async () => Task.FromResult("MYTOKEN");
            })
            .Build();

        _hubConnection.On<string, string>("ReceiveMessageAsync", AddMessage);
        await _hubConnection.StartAsync();
    }

    private void AddMessage(string username, string message)
    {
        var encodedMsg = $"{username}: {message}";
        _messages.Add(encodedMsg);
        OnMessagesUpdated?.Invoke();
    }

    public void Dispose()
    {
        if (_hubConnection != null)
        {
            _hubConnection.Off<string, string>("ReceiveMessageAsync", AddMessage);
            _ = _hubConnection.StopAsync();
            _ = _hubConnection.DisposeAsync();
        }
    }
}

步骤2:注册单例服务

在Program.cs中添加:

builder.Services.AddSingleton<ChatService>();

步骤3:修改组件代码

@page "/chat"
@rendermode InteractiveServer
@inject ChatService ChatService
@implements IDisposable

<ul>
    @foreach (var message in ChatService.Messages)
    {
        <li>@message</li>
    }
</ul>

代码后置:

protected override async Task OnInitializedAsync()
{
    await ChatService.Initialize(NavigationManager);
    ChatService.OnMessagesUpdated += HandleMessagesUpdated;
}

private void HandleMessagesUpdated()
{
    _ = InvokeAsync(StateHasChanged);
}

public void Dispose()
{
    ChatService.OnMessagesUpdated -= HandleMessagesUpdated;
}

2. 确保组件实例的SignalR连接正确清理

如果坚持在组件内部管理连接,需实现IDisposable清理连接,避免旧实例的回调继续操作无效的messages列表:

public async void Dispose()
{
    if (hubConnection != null)
    {
        hubConnection.Off<string, string>("ReceiveMessageAsync", ReceivedNewMessage);
        await hubConnection.StopAsync();
        await hubConnection.DisposeAsync();
    }
}

3. 验证同步上下文一致性

在SignalR回调和按钮点击方法中打印SynchronizationContext.Current,确认两者处于同一同步上下文。若不一致,可尝试在回调中使用Dispatcher.InvokeAsync替代InvokeAsync(需注入IDispatcher)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 22:40:08