当前位置: 首页 > news >正文

【C#】从 Queue 到 ConcurrentQueue:一次对象池改造的实战心得

背景

最近在做一个图像处理的 WPF 项目,底层使用 Halcon 的 HObject 来存放图像。为了减少频繁创建和释放对象带来的开销,我实现了一个对象池,用来存放 HObject,方便后续流程复用。

最初的实现用的是 .NET 自带的 Queue<T>

private readonly Queue<T> objects = new Queue<T>();

配合 lock 实现线程安全,在 GetObject 时取出一个对象,ReturnObject 时放回队列。

一切看似顺利,但随着功能扩展,我遇到了两个问题:

  1. 多线程调用时有潜在的性能瓶颈
    lock 保护了队列,但在高并发下会阻塞其他线程。

  2. 对象生命周期混乱
    在显示后立即归还对象时,有时会出现 CurImg 已经被释放的情况,导致后续流程无法使用。

于是,我开始思考:是不是可以用 ConcurrentQueue 来替代 Queue + lock


Queue vs ConcurrentQueue 对比

特性QueueConcurrentQueue
线程安全❌ 需要手动加 lock✅ 内置线程安全
性能在单线程或低并发下更快在多线程下更优,避免锁竞争
操作方式Enqueue / DequeueEnqueue / TryDequeue
Count 属性精确(单线程)近似值(多线程下可能不是实时)
适用场景单线程队列或少量锁保护的情况高并发读写队列、生产者-消费者模式

改造过程

我将原先的 Queue<T> 换成了 ConcurrentQueue<T>,并去掉了多余的 lock

原始版本(Queue + lock)

public T GetObject()
{lock (objects){if (objects.Count > 0)return objects.Dequeue();elsereturn new T();}
}

改造版本(ConcurrentQueue)

public T GetObject()
{if (objects.TryDequeue(out var obj)){return obj;}return new T();
}

改造后的好处

  1. 线程安全更自然
    ConcurrentQueue 内部使用了无锁算法,减少了阻塞等待的情况。

  2. 代码更简洁
    不再需要手动加 lock,也避免了忘记加锁导致的潜在 bug。

  3. 性能在多线程下更优
    多个线程可以同时安全地读写队列。


需要注意的坑

改造后,我也踩了几个坑:

  1. 不要先判断 Count 再操作
    在多线程下,Count 只是一个快照值。
    如果写成:

    if (objects.Count > 0)objects.TryDequeue(out var obj);
    

    就可能在判断到取出之间,队列已经被别的线程清空,导致逻辑不一致。

    正确写法:直接用 TryDequeue 判断并取值。

  2. Count 在容量控制上的延迟
    当多个线程同时 ReturnObject,可能短暂超过 _maxPoolSize
    我的处理方式是用 while 循环清理多余对象:

    while (objects.Count > _maxPoolSize && objects.TryDequeue(out var old))
    {if (old is IDisposable disposable)disposable.Dispose();
    }
    

    虽然会有一点“超限再回落”,但影响不大。


关于 HObject 的思考

在改造过程中,我发现 HObject 这种一次性资源(Dispose 后不可复用)其实不太适合放到传统意义的“对象池”里。
但是我为了自动化释放管理,同时不愿意立马释放当前图像,所以这么做了。


总结

从这次改造中,我有几点心得:

  • 如果是高并发的队列操作,ConcurrentQueue 是更优解,省去了手动加锁的麻烦。
  • 多线程下不要依赖 Count 做逻辑判断,直接用 TryDequeue 更安全。
  • 改造代码时,不要只关注语法,还要考虑资源生命周期,否则会出现“对象提前被释放”的问题。

💡 一句话总结

在多线程队列管理上,ConcurrentQueue 是比 Queue+lock 更简洁的选择,但资源的生命周期管理才是对象池真正的难点。

http://www.dtcms.com/a/327790.html

相关文章:

  • 激活函数篇(2):SwiGLU | GLU | Swish | ReLU | Sigmoid
  • 如何查看当前Redis的密码、如何修改密码、如何快速启动以及重启Redis (Windows)
  • 鹧鸪云:光伏施工流程管理的智能“导航仪”
  • 云平台监控-云原生环境Prometheus企业级监控实战
  • 【Redis与缓存预热:如何通过预加载减少数据库压力】
  • RoboNeo美图AI助手
  • 如何单独修改 npm 版本(不改变 Node.js 版本)
  • npm、pnpm、yarn区别
  • 深度解析Mysql的开窗函数(易懂版)
  • docker-compose安装ElasticSearch,ik分词器插件,kibana【超详细】
  • 夜莺开源监控,模板函数一览
  • 集合,完整扩展
  • 任务调度系统设计与实现:Quartz、XXL-JOB 和 Apache Airflow 对比与实践
  • 【项目设计】高并发内存池
  • windows系统端口异常占用删除教程
  • Go面试题及详细答案120题(0-20)
  • [TryHackMe]Internal(hydra爆破+WordPress主题修改getshell+Chisel内网穿透)
  • 《Q————Mysql连接》
  • Linux软件编程:IO(二进制文件)、文件IO
  • 【25-cv-08993】T Miss Toys 启动章鱼宠物玩具版权维权,15 项动物玩偶版权均需警惕
  • 如何使用gpt进行模式微调(2)?
  • 使用Spring Boot对接欧州OCPP1.6充电桩:解决WebSocket连接自动断开问题
  • 无文件 WebShell攻击分析
  • php+apache+nginx 更换域名
  • SpringCloud 核心内容
  • 82. 删除排序链表中的重复元素 II
  • 计算机网络摘星题库800题笔记 第4章 网络层
  • “冒险玩家”姚琛「万里挑一」特别派对 打造全新沉浸式户外演出形式
  • Javase 之 字符串String类
  • 亚马逊手工制品类目重构:分类逻辑革新下的卖家应对策略与增长机遇