如何在ASP.NET Core SignalR序列化前锁定共享对象?
多线程场景下SignalR序列化共享集合的线程安全问题
当存在被多线程操作的static List<int>对象时,不加锁直接枚举是不安全的。而从SignalR Hub方法返回该列表或发送给客户端时,SignalR会内部自动序列化且不会提前加锁,这会引发线程安全风险。
在ASP.NET Core ApiController中,我们可以通过自定义序列化流程规避该问题:
static List<int> list; object Get() { string s; lock(list) { s = JsonConvert.Serialize(list); } return new ContentResult(){ Content = s, ContentType = "application/json"}; }
这种方法虽不完美,但可解决问题。
针对SignalR场景,以下是几种解决方案的分析:
方案1:传递给SignalR前克隆对象
- 实现思路:在将共享对象返回给SignalR前,先创建对象副本,确保序列化操作针对独立副本,避免与其他线程的修改操作冲突。
- 缺点:克隆存在性能开销,集合元素较多时尤为明显;若为复杂对象,还需实现深克隆,增加代码复杂度。
方案2:利用Newtonsoft JSON的序列化回调与错误处理
在包含共享集合的类中定义序列化生命周期回调,序列化开始时加锁,完成或出错时释放锁:
// 从Hub方法返回的共享对象 class SomeObject { public List<int> list; [OnSerializing] internal void OnSerializingMethod(StreamingContext context) { Monitor.Enter(this); } [OnSerialized] internal void OnSerializedMethod(StreamingContext context) { Monitor.Leave(this); } [OnError] internal void OnError(StreamingContext context, ErrorContext errorContext) { Monitor.Leave(this); } }
- 注意事项:需确认
OnSerializing与OnSerialized、OnSerializing与OnError是否严格成对调用。若序列化过程中出现未捕获异常导致回调未触发,可能引发死锁风险。建议结合try/finally逻辑确保锁释放,或使用lock语法糖替代直接调用Monitor方法。
方案3:SyncRoot是否适用?
SyncRoot是ICollection接口定义的用于同步集合访问的对象,通常用于手动控制集合的线程安全访问。但SignalR的序列化流程由框架内部完成,无法直接在序列化过程中利用SyncRoot加锁——除非拦截序列化流程并手动使用SyncRoot加锁,这本质上和ApiController中自定义序列化的思路一致,只是换了锁对象。因此SyncRoot本身无法直接解决SignalR自动序列化的线程安全问题,需结合自定义序列化或回调机制使用。
内容的提问来源于stack exchange,提问作者ywq
相关产品推荐
相关产品推荐

