C# ConcurrentQueue使用方法 C#如何实现一个线程安全的FIFO队列

4次阅读

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

C# ConcurrentQueue使用方法 C#如何实现一个线程安全的FIFO队列

ConcurrentQueue 是线程安全的 FIFO 队列,无需额外加锁

它内部用无锁(lock-free)算法实现,所有公开方法(EnqueueTryDequeueTryPeek)天然线程安全。多个线程同时读写不会导致数据损坏或抛出 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() 等阻塞方法

多线程环境下真正容易被忽略的,是把 IsEmptyCount 当作同步信号来用。它们不是锁,也不是内存屏障,只反映调用那一纳秒的状态。

text=ZqhQzanResources