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

Blazor Server用户间消息传递:接收方异常致发送方崩溃求助

解决方案

针对Blazor Server中接收方处理消息异常导致发送方崩溃的问题,核心是切断发送方与接收方的异常传导链路,同时保证发送方无需关注后续处理状态。以下是几个实用的落地方案:


方案1:为每个事件订阅者添加独立异常捕获

问题根源在于触发MessageReceived事件时,若某个接收方的处理逻辑抛出未捕获异常,会沿着调用链回传到发送方的调用上下文。解决思路是在分发消息时,为每个订阅者的处理逻辑单独包裹异常捕获,确保单个接收方的异常不会影响其他订阅者和发送方。

修改NotifyMessageReceived方法示例:

public void NotifyMessageReceived(List<string> notifyIds, MessageDto messageDto)
{
    var handlers = MessageReceived;
    if (handlers == null) return;

    // 遍历所有订阅者,逐个调用并捕获异常
    foreach (var handler in handlers.GetInvocationList())
    {
        // 根据业务逻辑筛选需要通知的接收方(示例:从handler关联的用户ID判断)
        var receiverUserId = GetUserIdFromHandler(handler);
        if (!notifyIds.Contains(receiverUserId)) continue;

        try
        {
            handler.DynamicInvoke(this, new MessageReceivedEventArgs(messageDto));
        }
        catch (Exception ex)
        {
            // 记录异常日志,便于排查问题
            _logger.LogError(ex, "接收方 {UserId} 处理消息失败", receiverUserId);
        }
    }
}

方案2:异步事件+后台任务解耦

将消息处理逻辑放到独立的后台任务中执行,发送方调用后直接返回,不等待接收方处理完成,彻底切断同步调用的异常传导路径。

步骤1:定义异步事件

// 替换原有的同步事件
public event Func<object, MessageReceivedEventArgs, Task> MessageReceivedAsync;

步骤2:异步分发消息

public async Task NotifyMessageReceivedAsync(List<string> notifyIds, MessageDto messageDto)
{
    var handlers = MessageReceivedAsync;
    if (handlers == null) return;

    foreach (var handler in handlers.GetInvocationList().Cast<Func<object, MessageReceivedEventArgs, Task>>())
    {
        var receiverUserId = GetUserIdFromHandler(handler);
        if (!notifyIds.Contains(receiverUserId)) continue;

        // 用fire-and-forget的后台任务执行,发送方无需等待
        _ = Task.Run(async () =>
        {
            try
            {
                // 若接收方需要更新UI,必须通过InvokeAsync切换到Blazor电路上下文
                await handler.Invoke(this, new MessageReceivedEventArgs(messageDto));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "接收方 {UserId} 异步处理消息失败", receiverUserId);
            }
        });
    }
}

发送方调用时直接执行await _usersStateContainer.NotifyMessageReceivedAsync(notifyIds, messageDto)即可,调用完成后立即返回,不受接收方处理结果影响。


方案3:引入中间消息队列(彻底解耦)

通过内存队列或第三方队列实现发送方与接收方的完全解耦,发送方仅负责将消息写入队列,后续分发逻辑由后台服务独立处理,接收方的异常绝对不会传导到发送方。

示例:内存消息队列实现

public interface IMessageQueue
{
    void Enqueue(List<string> notifyIds, MessageDto messageDto);
}

public class InMemoryMessageQueue : IMessageQueue
{
    private readonly ConcurrentQueue<MessageQueueItem> _queue = new();
    private readonly IUsersStateContainer _usersStateContainer;
    private readonly ILogger<InMemoryMessageQueue> _logger;
    private readonly CancellationToken _stoppingToken;

    public InMemoryMessageQueue(IUsersStateContainer usersStateContainer, 
                               ILogger<InMemoryMessageQueue> logger, 
                               IHostApplicationLifetime appLifetime)
    {
        _usersStateContainer = usersStateContainer;
        _logger = logger;
        _stoppingToken = appLifetime.ApplicationStopping;
        // 启动队列处理后台任务
        _ = ProcessQueueAsync();
    }

    public void Enqueue(List<string> notifyIds, MessageDto messageDto)
    {
        _queue.Enqueue(new MessageQueueItem { NotifyIds = notifyIds, Message = messageDto });
    }

    private async Task ProcessQueueAsync()
    {
        while (!_stoppingToken.IsCancellationRequested)
        {
            if (_queue.TryDequeue(out var item))
            {
                try
                {
                    await _usersStateContainer.NotifyMessageReceivedAsync(item.NotifyIds, item.Message);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "队列消息分发失败,将重新入队");
                    // 失败消息重新入队,避免丢失
                    _queue.Enqueue(item);
                    await Task.Delay(1000, _stoppingToken);
                }
            }
            else
            {
                await Task.Delay(100, _stoppingToken);
            }
        }
    }

    private class MessageQueueItem
    {
        public List<string> NotifyIds { get; set; }
        public MessageDto Message { get; set; }
    }
}

发送方只需调用_messageQueue.Enqueue(notifyIds, messageDto)即可完成消息发送,后续所有分发、处理逻辑由后台服务独立执行,完全与发送方解耦。


关键注意事项

  • 若接收方处理逻辑涉及UI更新,必须调用InvokeAsync切换到Blazor电路上下文,避免线程异常(但该异常已被捕获,不会影响发送方)。
  • 所有异常必须记录日志,便于后续排查接收方的处理问题。
  • 若使用异步任务,务必在任务内部捕获所有异常,避免未处理异常导致应用崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 18:23:13