Blazor SignalR实现全局消息列表时重复添加消息问题求助
解决Blazor SignalR全局消息列表重复添加的问题
在实现Blazor SignalR聊天室时,为解决新客户端无法查看历史消息的问题,将消息存储从客户端本地列表改为单例ChatLog全局存储,但出现每条消息被重复添加X次(X为活跃客户端数量)的问题——原因是每个客户端的hubConnection.On<"ReceiveMessage">回调都会执行消息添加操作。
核心思路
将消息存储逻辑从客户端移至SignalR Hub服务器端,由服务器统一处理消息的存储与推送,客户端仅负责接收推送通知并刷新UI,避免多客户端重复写入全局列表。
步骤1:修改ChatHub,服务器端统一存储消息
注入单例ChatLog,在接收客户端发送的消息时,先将消息存入全局列表,再推送给所有客户端:
public class ChatHub : Hub { private readonly ChatLog _chatLog; public ChatHub(ChatLog chatLog) { _chatLog = chatLog; } public async Task SendMessage(string user, string message) { var formattedMessage = $"{user}: {message}"; // 服务器端统一添加消息(仅执行一次) _chatLog.AddMessage(formattedMessage); // 推送给所有客户端 await Clients.All.SendAsync("ReceiveMessage", user, message); } // 供客户端获取历史消息的方法 public List<string> GetHistoryMessages() { return _chatLog.Messages.ToList(); } }
步骤2:优化ChatLog的线程安全性
由于List<string>并非线程安全,为避免多线程操作导致的异常,修改ChatLog添加锁机制:
public class ChatLog { private readonly List<string> _messages = new List<string>(); private readonly object _lockObj = new object(); // 对外提供只读列表,防止客户端直接修改 public IReadOnlyList<string> Messages { get { lock (_lockObj) return _messages.AsReadOnly(); } } // 统一的添加方法,加锁保证线程安全 public void AddMessage(string message) { lock (_lockObj) { _messages.Add(message); } } }
步骤3:修改客户端逻辑,仅负责UI刷新与历史消息拉取
客户端不再在ReceiveMessage回调中添加消息,仅刷新UI;初始化时从服务器拉取历史消息:
@code{ private HubConnection hubConnection; [Inject] private ChatLog ChatLogger { get; set; } [Inject] private NavigationManager NavManager { get; set; } protected override async Task OnInitializedAsync() { hubConnection = new HubConnectionBuilder() .WithUrl(NavManager.ToAbsoluteUri("/chathub")) .WithAutomaticReconnect() .Build(); // 仅接收推送通知,刷新UI展示全局消息列表 hubConnection.On<string, string>("ReceiveMessage", (user, message) => { InvokeAsync(StateHasChanged); }); await hubConnection.StartAsync(); // 拉取历史消息,初始化客户端展示 var historyMessages = await hubConnection.InvokeAsync<List<string>>("GetHistoryMessages"); // 若需要客户端本地备份可在此赋值,否则直接绑定ChatLogger.Messages即可 } }
临时替代方案(不修改服务器端)
若暂时不想调整服务器逻辑,可在客户端添加消息前检查是否已存在(不推荐,效率低且可能有并发问题):
hubConnection.On<string, string>("ReceiveMessage", (user, message) => { var formattedMessage = $"{user}: {message}"; lock (ChatLogger.Messages) { if (!ChatLogger.Messages.Contains(formattedMessage)) { ChatLogger.Messages.Add(formattedMessage); } } InvokeAsync(StateHasChanged); });
此方法无法完全避免并发场景下的重复,且消息内容重复时会被误判,仅作为临时 workaround。
内容的提问来源于stack exchange,提问作者Inirem
相关产品推荐
相关产品推荐

