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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 09:31:12