Blazor中InstallServiceInfoReceived触发后OnServiceInfoReceived偶发未调用问题
相关代码片段
program.cs
builder.Services.AddScoped<ServiceInfoReceiveHandler>();
message_broker.cs
public MessageBroker(ServiceInfoReceiveHandler serviceInfoReceiveHandler) { _handlers = new Dictionary<string, IReceiveHandler> { { "SVCMGMT/INSTALL/SVCINFO", serviceInfoReceiveHandler }, }; ReceiveMessage(); } private void ReceiveMessage() { try { if (_handlers.ContainsKey(contentType)) { _handlers[contentType].Receive(ea); } else { throw new Exception($"No handler for content type '{contentType}'"); } } }
receive.cs
if (contentType == "") { InstallServiceInfoReceived?.Invoke(this, (contentType, serverName, message)); }
ServiceInfoReceiveHandler.cs
public class ServiceInfoReceiveHandler : IReceiveHandler { public event EventHandler<(string contentType, string serverName, string message)> InstallServiceInfoReceived; public void Receive(BasicDeliverEventArgs eventArgs) { var body = eventArgs.Body.ToArray(); var message = Encoding.UTF8.GetString(body); var serverName = eventArgs.BasicProperties.AppId; var contentType = eventArgs.BasicProperties.ContentType; if (contentType == "SVCMGMT/INSTALL/SVCINFO") { InstallServiceInfoReceived?.Invoke(this, (contentType, serverName, message)); } } }
install.razor
@inject ServiceInfoReceiveHandler _serviceInfoReceiveHandler protected override async Task OnInitializedAsync() { _serviceInfoReceiveHandler.InstallServiceInfoReceived += OnServiceInfoReceived; } public void Dispose() { _serviceInfoReceiveHandler.InstallServiceInfoReceived -= OnServiceInfoReceived; } private async void OnServiceInfoReceived(object sender, (string contentType, string serverName, string message) e) { ... }
问题描述
我正在开发Blazor项目,采用上述事件处理机制。大部分情况下该机制运行正常,但偶发出现OnServiceInfoReceived未被调用的情况。经调试发现,InstallServiceInfoReceived?.Invoke()始终会执行,但后续OnServiceInfoReceived有时会触发、有时不会。
该偶发情况似乎在我与其他开发者同时进行本地测试时出现,我们各自运行本地localhost实例,但共享消息队列和数据库。请问这种共享环境是否会导致该问题?
问题分析与解答
共享消息队列环境确实是导致该偶发现象的核心原因之一,结合你的代码设计,具体原因如下:
消息队列的消费分发逻辑
如果共享的消息队列采用轮询消费模式(比如RabbitMQ默认模式),消息会被轮流分配到不同的消费者实例(即你和同事的本地进程)。当一条消息被同事的实例消费时,你的Blazor应用对应的ServiceInfoReceiveHandler不会处理这条消息,自然不会触发你组件中的OnServiceInfoReceived。Scoped服务的实例隔离问题
你将ServiceInfoReceiveHandler注册为Scoped服务,意味着每个用户会话(Blazor Server)或组件生命周期(Blazor WebAssembly)都会创建独立实例。如果MessageBroker是单例服务,它注入的ServiceInfoReceiveHandler是应用启动时创建的单例实例,和你Blazor组件中注入的Scoped实例并非同一个。这会导致:- 消息被
MessageBroker持有的Handler实例处理并触发事件,但组件订阅的是另一个Handler实例的事件,收不到通知。 - 多实例运行时,这种实例不匹配的问题会被放大,偶发概率提升。
- 消息被
事件订阅时机的随机性
多人测试时消息产生时机更随机,如果组件的OnInitializedAsync执行晚于MessageBroker开始消费消息,事件已经触发但组件还没完成订阅,也会出现漏接收的情况。
解决方案建议
- 调整服务生命周期:将
ServiceInfoReceiveHandler注册为Singleton,确保MessageBroker和Blazor组件使用同一个实例:builder.Services.AddSingleton<ServiceInfoReceiveHandler>(); - 修改消息队列消费模式:如果需要每个实例都接收同一条消息,将消息队列设置为发布/订阅模式(比如RabbitMQ的Fanout交换机),确保所有消费者都能收到消息。
- 优化事件订阅时机:在Blazor应用启动阶段就完成事件订阅,避免组件初始化延迟导致的漏接收。
内容的提问来源于stack exchange,提问作者beginner

