简体   繁体   English

C#BlockingCollection生成者使用者而不阻塞使用者线程

[英]C# BlockingCollection producer consumer without blocking consumer thread

I have a situation where I need to have a large number (hundreds) of queues, where the items should be processed in order (need single threaded consumer). 我有一种情况,我需要有大量(数百)队列,其中的项目应按顺序处理(需要单线程消费者)。 My first implementation, based on the samples, I used a single long-running Task per BlockingCollection to consume the queue items. 我的第一个实现, 基于示例,我使用单个长时间运行的Task per BlockingCollection来使用队列项。 However, I ended up having an applications with hundreds of threads mostly sitting idle doing nothing but consuming memory, since the queues are empty most of the time. 但是,我最终得到了一个拥有数百个线程的应用程序,这些线程大部分都处于空闲状态,除了占用内存之外什么都不做

I thought it would be better to only have a consumer Task running only if there's something in the queue to process, however, I haven't been able to find samples that provide what the best practices should be. 我认为只有在队列中有东西需要处理才能运行消费者任务会更好,但是,我无法找到能够提供最佳实践的样本。

I came up with a solution similar to the one below. 我提出了类似于下面的解决方案。 But the problem is, every item results in a new Task (maybe this is inefficient? Waste of resources?). 但问题是,每个项目都会产生一个新任务(这可能是效率低下的?浪费资源?)。 But if I don't create a new task for every item, I can't guarantee that an item won't be sitting in the queue unprocessed. 但是如果我没有为每个项目创建一个新任务,我不能保证一个项目不会在未处理的队列中。

    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
             }
        }
    }

What are the best practices/best solution for this situation with a guarantee that no situation exists where an item is in the queue, but no consumer is running? 针对这种情况的最佳实践/最佳解决方案是什么,并保证项目在队列中没有任何情况,但没有消费者在运行?

I think that what you did is reasonable, because the Task was made to scale well also with million of tasks, producing internal sub-queues against the ThreadPool, avoiding too much context switching. 我认为你所做的是合情合理的,因为Task还可以很好地扩展到数百万个任务,针对ThreadPool产生内部子队列,避免过多的上下文切换。

Behind the scenes, tasks are queued to the ThreadPool, which has been enhanced with algorithms that determine and adjust to the number of threads and that provide load balancing to maximize throughput. 在幕后,任务排队到ThreadPool,后者已经使用算法进行了增强,这些算法可以确定并调整线程数,并提供负载平衡以最大化吞吐量。 This makes tasks relatively lightweight, and you can create many of them to enable fine-grained parallelism. 这使得任务相对轻量级,您可以创建其中的许多任务以实现细粒度的并行性。

Task Parallelism (Task Parallel Library) 任务并行(任务并行库)

...but what you did, will end up in just a normal Task programming, because for every enqueue you start a task, so the blocking collection is quite unused. ...但是你所做的,最终将只是一个正常的任务编程,因为对于每个入队你开始一个任务,所以阻塞集合是非常未使用的。 As far as understood, your concern is about firing a task and let the TaskScheduler, run the jobs in order as they arrived. 据了解,您关注的是关于触发任务并让TaskScheduler在他们到达时按顺序运行作业。

Do you know you can also customize the TaskScheduler ? 你知道你也可以自定义TaskScheduler吗?

What about just use a Task programming pattern, plus a custom TaskScheduler to control the flow of the scheduled task? 如何使用Task编程模式,以及自定义TaskScheduler来控制计划任务的流程呢?

For example you can create an OrderedTaskScheduler, that derive from a LimitedConcurrencyLevelTaskScheduler that would behave like this... 例如,您可以创建一个OrderedTaskScheduler,它派生自一个行为如此的LimitedConcurrencyLevelTask​​Scheduler ......

The LimitedConcurrencyLevelTaskScheduler class offers a task scheduler that ensures a maximum concurrency level while running on top of the ThreadPool. LimitedConcurrencyLevelTask​​Scheduler类提供了一个任务调度程序,可确保在ThreadPool之上运行时的最大并发级别。 It is necessary to set the maximum degree of parallelism desired for this scheduler. 必须设置此调度程序所需的最大并行度。

The OrderedTaskScheduler class provides a task scheduler that ensures only one task is executing at a time. OrderedTaskScheduler类提供了一个任务调度程序,可确保一次只执行一个任务。 Tasks execute in the order that they were queued (FIFO). 任务按排队顺序(FIFO)执行。 It is a subclass of LimitedConcurrencyLevelTaskScheduler that sends 1 as a parameter for its base class constructor. 它是LimitedConcurrencyLevelTask​​Scheduler的子类,它将1作为其基类构造函数的参数发送。

You can find these scheduler already developed, they're called ParallelExtensionsExtras , and you can download it from here , and read some toughts about it from this blog post and others . 您可以找到已经开发的这些调度程序,它们被称为ParallelExtensionsExtras ,您可以从这里下载它,并从这篇博文其他 文章中阅读一些有关它的文章

You can find it also directly on nuget and a code mirror on github . 您也可以直接在nuget上找到它,并在github上找到代码镜像。

Enjoy! 请享用! :) :)

Have you considered Parallel Extension Extras ? 你考虑过Parallel Extension Extras吗? I believe your scenario could be easily satisfied by QueuedTaskScheduler or ThreadPerTaskScheduler . 我相信QueuedTaskScheduler或ThreadPerTaskScheduler可以轻松满足您的场景。

Certainly quite a rewrite but did you consider doing it like this instead? 当然是相当重写,但你认为这样做呢?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM