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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 08:35:41