concurrentqueue适用于单进程内线程间暂存数据,如设备指令缓冲、api限流、日志聚合前内存暂存;不适用于需持久化、跨进程、重试或死信的场景。

ConcurrentQueue<t></t> 是单进程内最轻量的消息队列实现,但不是所有“要排队”的场景都该用它。
什么时候直接用 ConcurrentQueue<t></t> 就够了
你只需要在线程间暂存数据、不关心持久化、不跨进程、也不需要重试或死信——比如设备指令缓冲、API 请求限流、日志聚合前的内存暂存。
它的 Enqueue 和 TryDequeue 都是无锁 O(1) 操作,吞吐高、GC 友好,且不用装服务、配环境、引第三方包。
- 适用场景:后台任务暂存、硬件指令排队、Web API 入口限流
- 典型误用:把它当 RabbitMQ 用在微服务间传消息——进程一重启,全丢
- 别在 while 循环里空转
TryDequeue,CPU 会飙高;改用Channel<t></t>或加await Task.Delay(1)
MessageQueue(MSMQ)适合哪些 Windows 内网场景
Windows 原生支持、无需额外中间件、消息自动落盘、支持事务和优先级——但仅限局域网,且只能跑在 Windows Server 或启用了 MSMQ 功能的桌面系统上。
常见错误是直接用 MessageQueue.Create(queuePath) 创建私有队列却忘了路径格式:@".\Private$\MyQueue" 中的 .\ 表示本地机器,Private$ 是固定前缀,不能写成 private 或漏掉 $。
- 必须先启用 Windows 功能:“Microsoft Message Queue (MSMQ) 服务器”
- 私有队列路径必须含
Private$,公开队列需域控支持,开发阶段基本不用 -
XmlMessageFormatter反序列化失败时会抛InvalidOperationException,务必用 try/catch 包裹message.Body访问
RabbitMQ 的 BasicConsume 为什么容易卡死
默认事件回调是同步执行的,一旦你在 Received 里写了 await 却没用 async void(或用了但没处理异常),channel 就会阻塞,新消息进不来,旧消息无法 ACK/NACK。
v6.0+ 推荐换用 AsyncEventingBasicConsumer,它的 Received 事件支持 Func<... task></...>,内部自动 await 并正确处理失败。
- 必须调用
channel.BasicQos(0, 1, false)控制预取数量,否则消费者积压时内存暴涨 - 处理失败别 throw,要显式
model.BasicNack(deliveryTag, multiple: false, requeue: false) - 反序列化失败、DB 超时等应发往 DLX(dead-letter exchange),而不是让消息卡住
选错队列类型的真实代价
用 ConcurrentQueue 替代 RabbitMQ 做分布式重试,结果故障后消息全丢,连日志都没法查;用 MSMQ 跨公网通信,连接超时还查不出是防火墙还是 DNS 问题;RabbitMQ 不设 BasicQos,消费者 OOM 后整个队列停摆。
真正关键的不是“怎么写”,而是“消息丢了谁负责、重试几次、失败后人工怎么捞、监控看哪几个指标”。这些在选型时就得对齐,而不是等上线后补救。











