在C#多线程开发中,如果需要在多个线程之间传递数据,普通的非线程安全队列如Queue<T>在并发操作时会出现数据错乱、异常抛出等问题,而ConcurrentQueue作为System.Collections.Concurrent命名空间下的线程安全集合,天然支持多线程的入队和出队操作,是实现线程安全FIFO队列的最佳选择。

ConcurrentQueue的核心特性
ConcurrentQueue的设计目标是提供无锁或者细粒度锁的线程安全操作,它具备以下几个核心特点:
- 线程安全:所有公开方法都支持多线程并发调用,无需开发者手动添加lock语句
- FIFO顺序:严格遵循先进先出的规则,先入队的元素会先被取出
- 不支持随机访问:没有提供按索引获取元素的方法,只能通过出队操作获取头部元素
- 高效性:内部采用了分段存储等优化设计,在高并发场景下的性能表现优于手动加锁的普通队列
常用方法介绍
ConcurrentQueue提供了几个核心的实例方法,足以满足大部分使用场景:
入队操作:Enqueue
Enqueue方法用于将元素添加到队列的尾部,该方法没有返回值,永远会成功执行入队操作:
using System;
using System.Collections.Concurrent;
class Program
{
static void Main()
{
// 创建ConcurrentQueue实例
ConcurrentQueue<string> queue = new ConcurrentQueue<string>();
// 入队操作
queue.Enqueue("第一个元素");
queue.Enqueue("第二个元素");
Console.WriteLine("入队完成");
}
}
出队操作:TryDequeue
由于队列可能为空,所以ConcurrentQueue没有设计直接出队的方法,而是提供了TryDequeue方法,该方法会尝试取出队列头部的元素,如果成功取出返回true,否则返回false:
using System;
using System.Collections.Concurrent;
class Program
{
static void Main()
{
ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
queue.Enqueue(10);
queue.Enqueue(20);
// 尝试出队
if (queue.TryDequeue(out int result))
{
Console.WriteLine($"成功取出元素:{result}");
}
else
{
Console.WriteLine("队列为空,无法取出元素");
}
}
}
查看队头元素:TryPeek
如果只需要查看队列头部的元素,不需要将其移除,可以使用TryPeek方法,该方法同样返回bool值表示操作是否成功:
using System;
using System.Collections.Concurrent;
class Program
{
static void Main()
{
ConcurrentQueue<string> queue = new ConcurrentQueue<string>();
queue.Enqueue("测试元素");
if (queue.TryPeek(out string peekResult))
{
Console.WriteLine($"队头元素为:{peekResult}");
}
}
}
其他辅助方法
IsEmpty属性:判断队列是否为空,比获取Count属性更高效,适合在判断队列是否有元素时使用Count属性:获取队列中元素的总数量,该属性需要遍历内部所有分段,性能开销相对较高,非必要不建议频繁调用Clear方法:清空队列中的所有元素,该方法从.NET 6版本开始支持,低版本需要遍历出队实现清空
多线程场景下的使用示例
下面通过一个生产者消费者的示例,演示ConcurrentQueue在多线程场景下的正确使用方式,该示例中一个线程负责生产数据入队,另一个线程负责消费数据出队:
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
ConcurrentQueue<int> dataQueue = new ConcurrentQueue<int>();
CancellationTokenSource cts = new CancellationTokenSource();
// 生产者任务:每秒生产一个数据
Task producer = Task.Run(() =>
{
int count = 0;
while (!cts.Token.IsCancellationRequested)
{
dataQueue.Enqueue(count);
Console.WriteLine($"生产者入队:{count}");
count++;
Thread.Sleep(1000);
}
});
// 消费者任务:不断尝试取出数据
Task consumer = Task.Run(() =>
{
while (!cts.Token.IsCancellationRequested)
{
if (dataQueue.TryDequeue(out int data))
{
Console.WriteLine($"消费者出队:{data}");
}
else
{
Console.WriteLine("队列为空,等待新数据");
}
Thread.Sleep(500);
}
});
// 运行10秒后停止
await Task.Delay(10000);
cts.Cancel();
await Task.WhenAll(producer, consumer);
Console.WriteLine("程序结束");
}
}
上述示例中,生产者和消费者线程同时操作同一个ConcurrentQueue实例,没有出现任何线程安全问题,也无需手动添加lock语句,充分体现了ConcurrentQueue在多线程场景下的优势。
和普通Queue对比
| 对比项 | 普通Queue<T> | ConcurrentQueue<T> |
|---|---|---|
| 线程安全 | 不支持,多线程操作会抛出异常或数据错乱 | 原生支持,无需手动加锁 |
| 使用场景 | 单线程或外部手动加锁的多线程场景 | 多线程无锁并发操作场景 |
| 性能表现 | 单线程下性能更好,多线程加锁后性能下降明显 | 高并发场景下性能更优 |
| 空队列出队 | 会抛出InvalidOperationException异常 | 返回false,不会抛出异常 |
使用注意事项
- 不要对ConcurrentQueue使用lock语句,它的方法已经是线程安全的,额外加锁反而会降低性能
- 尽量避免频繁调用Count属性,如果需要判断队列是否有元素,优先使用IsEmpty属性
- 低版本.NET Framework中没有Clear方法,如果需要清空队列,可以通过循环调用TryDequeue直到队列为空实现
- ConcurrentQueue的元素出队后不会被自动销毁,需要开发者自行处理元素的生命周期,避免内存泄漏
ConcurrentQueue是C#中实现线程安全FIFO队列的首选方案,正确使用它可以大幅降低多线程数据同步的复杂度,提升代码的稳定性和性能。
C#_ConcurrentQueue线程安全队列FIFO队列多线程编程修改时间:2026-06-21 12:00:37