C# .NET 6依赖注入下抽象类事件订阅失效问题
问题分析与解决方案
问题核心
单例服务WsUserRequestHandler继承抽象类WebSocketHandler后,调用基类RequestReceived方法订阅事件时,该方法内部确认事件已成功订阅(日志显示OnRequestReceived != null),但Receive方法循环接收消息时,始终检测到OnRequestReceived为null,导致事件无法触发。
原因分析
最直接的原因是多线程环境下的委托可见性问题:
OnRequestReceived是实例级私有委托字段,默认无内存屏障保障线程间可见性。RequestReceived在一个线程中完成事件订阅,而Receive在独立线程中循环读取事件状态,受CPU缓存机制影响,Receive线程无法获取到RequestReceived线程对委托的修改,始终读取初始的null值。
另外需排查潜在风险:
- 确认
WsUserRequestHandler的单例配置是否生效,可在类构造函数中添加日志验证是否仅初始化一次; - 确认
Connect方法未被多次调用,避免启动多个独立的Receive循环(即使多次调用,核心问题仍为可见性)。
解决方案
方案1:用volatile修饰委托字段
为OnRequestReceived添加volatile修饰符,强制线程每次读取该字段时从主内存获取最新值,绕过CPU缓存:
private volatile EventHandler<OnRequestReceivedEventArgs> OnRequestReceived;
方案2:用锁同步事件访问
通过锁将事件的读写操作纳入同步上下文,彻底解决线程可见性问题:
// 在WebSocketHandler中新增锁对象 private readonly object _eventSyncLock = new object(); private EventHandler<OnRequestReceivedEventArgs> OnRequestReceived; // 修改RequestReceived中的事件订阅/取消逻辑 protected async Task<TRequest> RequestReceived<TRequest>(CancellationToken token) where TRequest : IWebSocketRequest { var request = default(TRequest); void ReceivedRequest(object sender, OnRequestReceivedEventArgs args) { if (args.Request.GetType() == typeof(TRequest)) request = (TRequest)args.Request; } // 加锁订阅事件 lock(_eventSyncLock) { this.OnRequestReceived += ReceivedRequest; } Console.WriteLine(this.OnRequestReceived == null); while (request == null) await Task.Delay(25, token); // 加锁取消订阅 lock(_eventSyncLock) { this.OnRequestReceived -= ReceivedRequest; } return request; } // 修改Receive中的事件触发逻辑 private async Task Receive(ClientWebSocket client) { while (true) { var result = await client.ReceiveAsync(...); if (result.MessageType == WebSocketMessageType.Text) { var jsonInput = Encoding.UTF8.GetString(...); var request = JsonConvert.DeserializeObject<IWebSocketRequest>(jsonInput, ...); if (request is null) continue; // 加锁读取事件委托 EventHandler<OnRequestReceivedEventArgs> handler; lock(_eventSyncLock) { handler = this.OnRequestReceived; } Console.WriteLine("received request event is null: " + (handler == null)); handler?.Invoke(this, new OnRequestReceivedEventArgs(request)); } // 新增:根据WebSocket关闭状态退出循环,避免资源泄漏 else if (result.MessageType == WebSocketMessageType.Close) { await client.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); break; } } }
额外优化建议
- 将
RequestReceived中的dynamic替换为TRequest,提升类型安全性; - 在
Receive方法中新增WebSocket关闭状态判断,避免无限循环导致的资源泄漏。
内容的提问来源于stack exchange,提问作者Markus Albinsson
相关产品推荐
相关产品推荐

