[英]C# BlockingCollection producer consumer without blocking consumer thread
我有一种情况,我需要有大量(数百)队列,其中的项目应按顺序处理(需要单线程消费者)。 我的第一个实现, 基于示例,我使用单个长时间运行的Task per BlockingCollection来使用队列项。 但是,我最终得到了一个拥有数百个线程的应用程序,这些线程大部分都处于空闲状态,除了占用内存之外什么都不做
我认为只有在队列中有东西需要处理才能运行消费者任务会更好,但是,我无法找到能够提供最佳实践的样本。
我提出了类似于下面的解决方案。 但问题是,每个项目都会产生一个新任务(这可能是效率低下的?浪费资源?)。 但是如果我没有为每个项目创建一个新任务,我不能保证一个项目不会在未处理的队列中。
private object _processSyncObj = new object();
private volatile bool _isProcessing;
private BlockingCollection<string> _queue = new BlockingCollection<string>();
private void EnqueueItem(string item)
{
_queue.Add(item);
Task.Factory.StartNew(ProcessQueue);
}
private void ProcessQueue()
{
if (_isProcessing)
return;
lock (_processSyncObj)
{
string item;
while (_isProcessing = _queue.TryTake(out item))
{
// process item
}
}
}
针对这种情况的最佳实践/最佳解决方案是什么,并保证项目在队列中没有任何情况,但没有消费者在运行?
我认为你所做的是合情合理的,因为Task还可以很好地扩展到数百万个任务,针对ThreadPool产生内部子队列,避免过多的上下文切换。
在幕后,任务排队到ThreadPool,后者已经使用算法进行了增强,这些算法可以确定并调整线程数,并提供负载平衡以最大化吞吐量。 这使得任务相对轻量级,您可以创建其中的许多任务以实现细粒度的并行性。
...但是你所做的,最终将只是一个正常的任务编程,因为对于每个入队你开始一个任务,所以阻塞集合是非常未使用的。 据了解,您关注的是关于触发任务并让TaskScheduler在他们到达时按顺序运行作业。
你知道你也可以自定义TaskScheduler
吗?
如何使用Task编程模式,以及自定义TaskScheduler来控制计划任务的流程呢?
例如,您可以创建一个OrderedTaskScheduler,它派生自一个行为如此的LimitedConcurrencyLevelTaskScheduler ......
LimitedConcurrencyLevelTaskScheduler类提供了一个任务调度程序,可确保在ThreadPool之上运行时的最大并发级别。 必须设置此调度程序所需的最大并行度。
OrderedTaskScheduler类提供了一个任务调度程序,可确保一次只执行一个任务。 任务按排队顺序(FIFO)执行。 它是LimitedConcurrencyLevelTaskScheduler的子类,它将1作为其基类构造函数的参数发送。
您可以找到已经开发的这些调度程序,它们被称为ParallelExtensionsExtras ,您可以从这里下载它,并从这篇博文和其他 文章中阅读一些有关它的文章 。
您也可以直接在nuget上找到它,并在github上找到代码镜像。
请享用! :)
你考虑过Parallel Extension Extras吗? 我相信QueuedTaskScheduler或ThreadPerTaskScheduler可以轻松满足您的场景。
当然是相当重写,但你认为这样做呢?
public class WorkerQueue<T>
{
public WorkerQueue(Action<T> workerMethod)
{
_workerMethod = workerMethod;
Task.Factory.StartNew(WorkerAction);
}
private Action<T> _workerMethod;
private void WorkerAction()
{
lock (_processSyncObj)
{
if (_workerMethod == null)
return;
while (true)
{
T item;
if (_queue.TryTake(out item))
{
var method = _workerMethod;
if (method != null)
method(item);
}
}
}
}
private BlockingCollection<T> _queue = new BlockingCollection<T>();
private object _processSyncObj = new object();
private volatile bool _isProcessing;
public void EnqueueItem(T item)
{
// thought you might want to swap BlockingCollection with a normal collection since you apparently only want your read threadlocked? You're already making that sure in "WorkerAction"
_queue.Add(item);
}
}
/// <summary>
/// Usage example
/// </summary>
public class Program
{
public void Start()
{
var test = new WorkerQueue<string>(WorkerMethod);
}
private void WorkerMethod(string s)
{
Console.WriteLine(s);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.