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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 14:36:19