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
相关产品推荐
相关产品推荐

