C# 任务队列简介
在现代多线程编程中,任务队列(task queue)是组织异步数据处理的基础模式。在 C# (.NET) 中,该模式通过多种强大机制实现,使开发者能够高效管理执行线程并分配负载。
任务队列解决了经典的 producer-consumer(生产者-消费者)问题,其中一部分代码创建任务,另一部分代码处理任务。在本文中,我们将探讨 C# 中实现任务队列的主要方式:从简单集合到现代通道(Channel)。
实现任务队列的主要方法
1. ConcurrentQueue — 线程安全队列
最简单且低级的实现是来自 System.Collections.Concurrent 命名空间的 ConcurrentQueue<T>。它提供了线程安全的 Enqueue 和 TryDequeue 方法。
using System;using System.Collections.Concurrent;using System.Threading.Tasks;
public class SimpleTaskQueue{ private readonly ConcurrentQueue<Action> _tasks = new(); private readonly Task _worker;
public SimpleTaskQueue() { _worker = Task.Run(ProcessTasks); }
public void Enqueue(Action task) { _tasks.Enqueue(task); }
private void ProcessTasks() { while (true) { if (_tasks.TryDequeue(out var task)) { task(); } } }}优点:
- 使用简单
- 内存消耗低
- 无锁线程安全
缺点:
- 循环中的忙等待(busy waiting)
- 缺少新任务通知机制
- 停止操作复杂
2. BlockingCollection — 阻塞队列
BlockingCollection<T> 解决了忙等待问题。它提供了 Add 和 Take 方法,这些方法会在数据可用前阻塞线程。
using System;using System.Collections.Concurrent;using System.Threading.Tasks;
public class BlockingTaskQueue : IDisposable{ private readonly BlockingCollection<Action> _tasks = new(new ConcurrentQueue<Action>()); private readonly Task[] _workers; private bool _disposed;
public BlockingTaskQueue(int workerCount = 4) { _workers = new Task[workerCount]; for (int i = 0; i < workerCount; i++) { _workers[i] = Task.Run(ProcessTasks); } }
public void Enqueue(Action task) { _tasks.Add(task); }
private void ProcessTasks() { foreach (var task in _tasks.GetConsumingEnumerable()) { if (_disposed) break; task(); } }
public void CompleteAdding() { _tasks.CompleteAdding(); }
public void Dispose() { _disposed = true; _tasks.CompleteAdding(); Task.WaitAll(_workers); _tasks.Dispose(); }}关键特性:
- 无任务时自动阻塞线程
- 支持通过
CancellationToken取消 - 可限制队列容量
- 通过
CompleteAdding安全终止
现代方法:Channel<T>
从 .NET Core 3.0 和 .NET 5 开始,C# 引入了 System.Threading.Channels.Channel<T> — 这是最高性能和最灵活的任务队列实现。它支持异步操作、取消和限制吞吐量。