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

C# Web API 2多游戏实例SSE推送触发事件后客户端异常问题

实现问题的核心定位与修正方案

现存实现的致命问题

  • 控制器实例字段的时序错乱
    你使用的private Game mGame是Web API控制器的实例字段,而Web API控制器本身是「每请求新建、请求返回即销毁」的短生命周期对象。你的OnStreamAvailable回调是由PushStreamContent在异步阶段触发的,此时控制器实例往往已经被请求管线回收,字段捕获的值不仅会出现跨请求的错乱,甚至可能因为内存复用读到其他请求的Game对象引用。
  • 事件订阅的内存与逻辑泄漏
    你在每次客户端发起订阅请求时,都会执行mGame.GameEvent += game_Event,但全程没有实现订阅移除逻辑。这会导致同一个静态事件处理器被重复绑定N次,一次游戏事件触发时,处理器会被重复调用数十甚至上百次,反复向同一个流写入重复消息;同时静态引用会持有Game对象的强引用,导致游戏对局对象永远无法被垃圾回收,随着对局数量增加内存持续上涨。
  • async void的线程安全隐患
    你写的静态事件处理器返回值是async void,这是C#异步编程的典型坑:这类方法内部抛出的未捕获异常会直接崩溃当前工作线程,没有任何捕获和回溯的手段。一旦流写入时遇到网络断开、IO错误,整个流程会直接崩溃,客户端连接自然锁死。
  • ConcurrentBag的错误操作
    ConcurrentBag是无序的线程安全集合,你在捕获到写流异常时,调用TryTake移除集合元素的逻辑是完全错误的——这个方法会随机取出集合里的任意元素,大概率会把其他正常工作的客户端连接移除,连带销毁正常连接的StreamWriter,直接导致大量无意义的断连。而服务端因为异常没有正确处理,返回500错误时不会带上配置的跨域响应头,浏览器自然会报CORS错误,这不是真的跨域配置失效。
  • SSE协议适配的细节错误
    你初始化StreamWriter时用了默认配置,默认的UTF8编码带BOM、不自动flush,会导致SSE消息格式不符合协议要求,客户端解析失败后主动断开连接。

针对性修正步骤

  1. 移除控制器上的mGame实例字段,直接在回调中传递当次请求对应的Game对象引用,避免跨请求的字段错乱。
  2. 调整Game类的事件签名,把原本的void返回值改成可等待的Task返回值,避免async void的异常崩溃问题;同时针对每个单独的订阅连接创建专属的事件处理逻辑,在客户端断连、流写入失败时,第一时间从Game的事件列表中移除对应订阅,彻底解决重复绑定的问题:
    // 调整Game类的事件定义,支持异步等待
    public event Func<Game, EventId, int, Task> OnGameEvent;
    
    // 触发事件的逻辑
    protected async Task RaiseGameEvent(EventId eventId, int index)
    {
        // 依次等待所有订阅者的逻辑执行完成,避免async void的无追踪异常
        if (OnGameEvent != null)
        {
            await OnGameEvent.Invoke(this, eventId, index);
        }
    }
    
  3. 替换连接存储的实现,用ConcurrentDictionary<Guid, Tuple<Game, StreamWriter>>存储连接列表,每个连接分配唯一的GUID标识,出现写流异常时直接定位到对应标识的连接元素移除,不再依赖无顺序的TryTake操作。
  4. 初始化StreamWriter时,使用无BOM的UTF8编码,开启自动flush,严格匹配SSE的文本流协议要求:
    // 初始化符合SSE要求的StreamWriter
    var writer = new StreamWriter(
        stream: stream, 
        encoding: new UTF8Encoding(false) // 不带BOM,避免消息解析失败
    )
    {
        AutoFlush = true // 自动刷新缓冲区,避免消息卡在缓冲区发不出去
    };
    
  5. 全局捕获SSE流处理过程中的所有IO异常,异常发生时正确关闭连接、清理订阅,保证500错误时依然带上预先配置的跨域响应头,解决浏览器误报的CORS问题。

注意:你参考的基础SSE示例之所以能正常运行,是因为它用定时器触发、没有多实例绑定、没有重复事件订阅的逻辑,所以把定时器换成游戏事件后,原本埋的几个隐患就会全部暴露出来。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.30 22:45:10