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

如何在异步多任务场景下正确使用ArrayPool管理相机图像缓存

解决方案:用引用计数包装池化缓冲区实现自动归还

核心思路

创建一个封装池化数组的包装类,通过引用计数跟踪缓冲区的使用者数量,当最后一个使用者完成操作后自动将缓冲区归还到ArrayPool,既避免大对象堆(LOH)压力,又支持异步多场景使用。

具体实现

1. 池化缓冲区包装类

这个类负责管理数组的租用、引用计数和自动归还,实现IDisposable接口确保资源被正确释放:

public sealed class PooledImageBuffer : IDisposable
{
    private readonly byte[] _pooledBuffer;
    private int _referenceCount;
    private bool _isReturnedToPool;

    // 存储实际图像大小,避免客户端访问池化数组的冗余部分
    public int ActualImageSize { get; }

    // 对外提供只读的Memory<T>,防止修改原始缓冲区
    public ReadOnlyMemory<byte> ImageData => _pooledBuffer.AsMemory(0, ActualImageSize);

    private PooledImageBuffer(byte[] pooledBuffer, int imageSize)
    {
        _pooledBuffer = pooledBuffer;
        ActualImageSize = imageSize;
        _referenceCount = 1;
    }

    // 从ArrayPool租用缓冲区并创建包装实例
    public static PooledImageBuffer Rent(int requiredSize)
    {
        var buffer = ArrayPool<byte>.Shared.Rent(requiredSize);
        return new PooledImageBuffer(buffer, requiredSize);
    }

    // 复制引用(增加计数),供多个异步操作共享同一缓冲区
    public PooledImageBuffer Share()
    {
        if (_isReturnedToPool)
            throw new ObjectDisposedException(nameof(PooledImageBuffer));
        
        Interlocked.Increment(ref _referenceCount);
        return this;
    }

    // 释放引用(减少计数),计数归零时归还缓冲区
    public void Dispose()
    {
        if (_isReturnedToPool)
            return;
        
        if (Interlocked.Decrement(ref _referenceCount) == 0)
        {
            ArrayPool<byte>.Shared.Return(_pooledBuffer);
            _isReturnedToPool = true;
        }
    }

    // 兜底Finalizer:防止客户端忘记Dispose导致缓冲区泄漏
    ~PooledImageBuffer()
    {
        if (!_isReturnedToPool)
        {
            // 可在此添加日志,排查未正确释放的场景
            ArrayPool<byte>.Shared.Return(_pooledBuffer);
            _isReturnedToPool = true;
        }
    }
}

2. 修改相机采集循环

在循环中租用包装类,通过Share()方法传递给事件订阅者,确保原始实例的引用计数正确:

while (IsLive)
{
    var imageSize = camera.GetParamInt(PRM.IMAGE_PAYLOAD_SIZE);
    using var rootBuffer = PooledImageBuffer.Rent(imageSize);
    
    // 将图像数据写入池化缓冲区
    camera.GetImage(rootBuffer._pooledBuffer);
    
    // 向所有订阅者传递共享引用
    ImageReceivedEvent?.Invoke(this, rootBuffer.Share());
}

3. 客户端使用示例

无论是同步UI更新还是异步图像处理,都通过using语句确保引用被正确释放:

WPF实时显示(同步)

private void OnImageReceived(object sender, PooledImageBuffer buffer)
{
    using (buffer)
    {
        // 转换为WPF BitmapSource(仅使用实际图像大小的内存)
        var bitmap = BitmapSource.Create(
            imageWidth, imageHeight, 96, 96, PixelFormats.Bgr24, null,
            buffer.ImageData.Span, imageStride);
        
        // 切换到UI线程更新显示
        Dispatcher.Invoke(() => LiveImageControl.Source = bitmap);
    }
}

异步图像处理(多任务)

private async Task ProcessImageFeaturesAsync(PooledImageBuffer buffer)
{
    using (buffer)
    {
        await Task.Run(() =>
        {
            // 图像特征识别
            var featureDetector = new FeatureDetector();
            var features = featureDetector.Detect(buffer.ImageData.Span);
            
            // 处理识别结果
            UpdateFeatureResults(features);
        });
    }
}

// 同时触发多个异步任务
private void OnImageReceived(object sender, PooledImageBuffer buffer)
{
    // 每个任务共享同一缓冲区,引用计数自动增加
    _ = ProcessImageFeaturesAsync(buffer.Share());
    _ = CheckFocusAsync(buffer.Share());
    _ = ScanQrCodeAsync(buffer.Share());
}

关键注意事项

  • 线程安全:使用Interlocked操作确保引用计数在多线程场景下的正确性
  • 避免越界访问:通过ImageData暴露只读的Memory<T>,仅包含实际图像数据的范围
  • 异常安全:using语句确保即使客户端代码抛出异常,也会正确释放引用
  • 兜底机制:Finalizer作为最后防线,防止因客户端忘记调用Dispose导致的缓冲区泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 18:50:53