concurrentqueue 是线程安全的无锁 fifo 队列,所有公开方法天然线程安全;必须用 trydequeue 而非 dequeue(后者不存在);count 和 isempty 非原子,仅作参考,不可用于循环条件或业务逻辑判断。

ConcurrentQueue 是线程安全的 FIFO 队列,无需额外加锁
它内部用无锁(lock-free)算法实现,所有公开方法(Enqueue、TryDequeue、TryPeek)天然线程安全。多个线程同时读写不会导致数据损坏或抛出 InvalidOperationException,也无需用 lock 包裹——加锁反而会破坏其性能优势,还可能引入死锁。
必须用 TryDequeue 而不是 Dequeue 来安全取值
ConcurrentQueue<t></t> 没有公开的 Dequeue 方法,只有 TryDequeue(out T result)。这是设计使然:队列可能在调用瞬间为空,直接返回值会导致异常或语义不清。
-
TryDequeue返回bool表示是否成功,成功时才把值写入out参数 - 错误写法:
var item = queue.Dequeue();—— 编译不通过 - 正确写法:
if (queue.TryDequeue(out var item)) { /* 使用 item */ } - 注意:即使返回
false,也不能说明队列“长期为空”,只是调用那一刻没元素
Count 和 IsEmpty 不是原子快照,仅作参考
Count 属性和 IsEmpty 属性在高并发下可能立即过期。例如:线程 A 读到 IsEmpty == false,紧接着线程 B 把最后一个元素出队,A 再调用 TryDequeue 就可能失败。
- 不要用
while (!queue.IsEmpty) { queue.TryDequeue(...) }做循环消费——这会漏数据或无限循环 - 更稳妥的消费模式是持续
TryDequeue,直到返回false,再短暂等待或退出 -
Count主要用于监控或调试,不适合做业务逻辑分支依据
初始化与常见误用场景
构造函数无参数,泛型类型 T 必须是引用类型或可空值类型(否则 out 参数赋值会出问题,比如 int? 可以,int 也可以,但要注意默认值)。
- 推荐初始化:
var queue = new ConcurrentQueue<int>();</int> - 避免把
ConcurrentQueue当作普通Queue用:它不支持索引访问、ToArray()返回的是快照,且不保证顺序绝对严格(虽实际几乎总是 FIFO) - 不要在循环中反复调用
Count判断是否“该停止”——这既慢又不可靠 - 若需阻塞式消费(如生产者-消费者模型),应配合
BlockingCollection<t></t>使用,它包装ConcurrentQueue并提供Take()等阻塞方法
多线程环境下真正容易被忽略的,是把 IsEmpty 或 Count 当作同步信号来用。它们不是锁,也不是内存屏障,只反映调用那一纳秒的状态。










