C# Channel Writer关闭时如何避免写入线程抛出异常?
解决C# Channels关闭时写入线程抛异常的问题
针对你用Channels做相机帧缓冲时遇到的关闭竞态问题,直接给出关键修正方案:
1. 修复事件订阅的关闭逻辑(避免误关)
现有代码每次移除订阅就调用CloseBuffer(),会导致还有其他订阅者时Channel被误关闭。改用原子计数器跟踪订阅数,仅当最后一个订阅者取消时才关闭Channel:
private int _subscriberCount; private event EventHandler<> _frameSetEvent; public event EventHandler<> FrameSetEvent { add { if (Interlocked.Increment(ref _subscriberCount) == 1) { StartBmpFrameBuffer(); // 仅第一个订阅时启动缓冲 } _frameSetEvent += value; } remove { _frameSetEvent -= value; if (Interlocked.Decrement(ref _subscriberCount) == 0) { _frameBuffer.CloseBuffer(); // 最后一个订阅取消时才关闭 } } }
2. 消除写入时的竞态问题
不要用_bufferRunning先检查再写入的方式,因为检查和写入不是原子操作,必然存在竞态。直接用以下两种方式处理:
方案A:使用TryWrite(同步/异步场景通用)
public bool AddItem(T item) { return _mainChannel.Writer.TryWrite(item); } // 调用时无需await,写入失败直接忽略(Channel已关闭属于预期情况) private async Task AddCamFrameSet2BmpBuffer(CvCameraFrameSetEvtArgs camFrameSet) { if (_subscriberCount == 0 || _frameBuffer == null) return; // 处理帧数据的代码 _frameBuffer.AddItem(camVisionFrameSet); }
方案B:捕获ChannelClosedException(保留异步写入)
如果必须用异步写入,捕获关闭异常并忽略:
public async Task AddItemAsync(T item) { try { await _mainChannel.Writer.WriteAsync(item); } catch (ChannelClosedException) { // Channel已关闭,无需处理直接返回 } }
3. 优化CloseBuffer实现
确保关闭Channel时正确释放资源,让消费者任务优雅退出:
public void CloseBuffer() { _mainChannel.Writer.Complete(); // 可选:等待消费者任务完成后再释放资源 // await _mainChannel.Reader.Completion; }
关键说明
- 原子计数器
_subscriberCount确保订阅数统计线程安全,避免多线程增减时的计数错误。 - 直接使用TryWrite或捕获异常,从根本上消除了"检查-写入"的竞态窗口,Channels自身的写入方法是线程安全的。
- 仅在最后一个订阅者取消时关闭Channel,避免影响其他仍在订阅的消费者。
内容的提问来源于stack exchange,提问作者JuanDYB
相关产品推荐
相关产品推荐

