简体   繁体   English

生产者/消费者,BlockingCollection和等待更改

[英]Producer/Consumer, BlockingCollection, and waiting for changes

I'm trying to wrap my head around BlockingCollection and my producer/consumer problem. 我试图围绕BlockingCollection和生产者/消费者问题进行讨论。

What I want to achieve, is the following: 我要实现的目标如下:

  • A thread-safe queue of sorts to hold a list of objects ("jobs") in FIFO manner. 各种线程安全队列,以FIFO方式保存对象列表(“作业”)。
  • A second thread-safe queue, which holds a list of results of those jobs, also in FIFO manner. 第二个线程安全队列也以FIFO方式保存这些作业的结果列表。

In other words: 换一种说法:

Inbound "Job" Data, can come at any time from multiple threads 
   ==> Thread-Safe FIFO Queue 1 "FQ1"
      ==> Async Processing of data in FQ1 (and remove item from FQ1)
         ==> Callback/Results into Thread-Safe FIFO Queue 2 "FQ2"
            ==> Async Processing of data in FQ2 (and remove item from FQ2)
               ==> Done

My humble tries so far are: 到目前为止,我的拙劣尝试是:

private BlockingCollection<InboundObject> fq1;
private BlockingCollection<ResultObject> fq2;

(...)

Task.Factory.StartNew(() =>
{
    foreach (InboundObject a in fq1.GetConsumingEnumerable())
       a.DoWork(result => fq2.Add(result)); //a.DoWork spits out an Action<ResultObject>
}

One of the reasons I chose BlockingCollection is because I want to keep load to a minimum, meaning only do work (and not deal with wait/sleep) when items are actually inside of the collection. 我选择BlockingCollection的原因之一是因为我希望将负载保持在最低水平,这意味着仅当项目实际上在集合中时才起作用(而不处理等待/睡眠)。 I'm not sure if foreach is the correct approach for that. 我不确定foreach是否是正确的方法。

Please let me know if this is correct or if there is a better approach. 请让我知道这是否正确或是否有更好的方法。 Thanks! 谢谢!

Edit I could tell from unit testing that the work inside the task was actually synchronous. 编辑我可以从单元测试中看出,任务内部的工作实际上是同步的。 New version is as follows: 新版本如下:

Task.Factory.StartNew(() =>
{
    foreach (InboundObject a in fq1.GetConsumingEnumerable())
       Task.Factory.StartNew(async () => { fq2.Add(await a.DoWork()); });
}

Input is very much appreciated! 输入非常感谢!

One of the reasons I chose BlockingCollection is because I want to keep load to a minimum, meaning only do work (and not deal with wait/sleep) when items are actually inside of the collection. 我选择BlockingCollection的原因之一是因为我希望将负载保持在最低水平,这意味着仅当项目实际上在集合中时才起作用(而不处理等待/睡眠)。 I'm not sure if foreach is the correct approach for that. 我不确定foreach是否是正确的方法。

This is right approach, foreach will be blocked until new item will be added to the queue or CompleteAdding method will be called. 这是正确的方法,在将新项目添加到队列中或将调用CompleteAdding方法之前, foreach将被阻止。 Not right is that you want achieve asynchronous processing with BlockingCollection. 不正确的是您想使用BlockingCollection实现异步处理。 BlockingCollection is a simple producer/consumer queue and must be used when you need to maintain the order in which jobs and job results are processed. BlockingCollection是一个简单的生产者/消费者队列,需要维护作业和作业结果的顺序时必须使用BlockingCollection。 Because of that it is synchronous. 因此,它是同步的。 Jobs will be processed in the same order that they was added. 作业将按照添加时的相同顺序进行处理。

If all you need is async execution you doesn't need a queue. 如果您需要的只是异步执行,则不需要队列。 You can use TPL in this case, just spawn a new task for each job, they will be queued by the TPL internally and will use as much OS threads as your system can handle efficiently. 在这种情况下,您可以使用TPL,只需为每个作业生成一个新任务,它们就会由TPL在内部排队,并且将使用系统可以有效处理的尽可能多的OS线程。 Your jobs can spawn their own tasks, for example. 例如,您的工作可以产生自己的任务。 This is much more flexible approach. 这是更加灵活的方法。

Also, producer/consumer queue can be used to organize pipeline execution of the jobs. 同样,生产者/消费者队列可用于组织作业的管道执行。 In this case job must be split to several steps. 在这种情况下,必须将作业分为几个步骤。 Each step must be performed by dedicated thread. 每个步骤必须由专用线程执行。 In every job-step thread we must read jobs from one queue, execute this job and than en-queue it to the next queue. 在每个作业步骤线程中,我们必须从一个队列中读取作业,执行该作业,然后将其排队进入下一个队列。

interface IJob
{
    void Step1();
    void Step2();
    ...
}

var step1 = new BlockingCollection<IJob>();
var step2 = new BlockingCollection<IJob>();
...

Task.Factory.StartNew(() =>
    {
        foreach(var step in step1.GetConsumingEnumerable()) {
            step.Step1();
            step2.Add(step);
        }
    });

Task.Factory.StartNew(() =>
    {
        foreach(var step in step2.GetConsumingEnumerable()) {
            // while performing Step2, another thread can execute Step1
            // of the next job
            step.Step2();
            step3.Add(step);
        }
    });

In this case jobs will be executed in FIFO order but in parallel. 在这种情况下,作业将以FIFO顺序但并行执行。 But If you want to do pipeline processing, you must first think about load balancing. 但是,如果要进行管道处理,则必须首先考虑负载平衡。 If one of the steps takes too much time it's queue will grow large and other threads will be idle most of the time. 如果其中一个步骤花费了太多时间,则它的队列将变大,而其他线程在大多数情况下将处于空闲状态。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 带有BlockingCollection的消费者/生产者看起来很慢 - Consumer/Producer with BlockingCollection appears slow 在BlockingCollection生产者消费者中完成与IDisposable的比较 - Finalize vs. IDisposable in BlockingCollection Producer Consumer 如何使用BlockingCollection <>解决生产者/消费者竞争条件 - How to solve producer/consumer race condition with BlockingCollection<> 生产者消费者有单独的类,具有共同的BlockingCollection - Producer Consumer Separate Classes with common BlockingCollection 无需BlockingCollection的简单生产者-消费者集合 - Simple producer-consumer collection without BlockingCollection 如何实现BlockingCollection来修复此生产者/消费者问题? - How to implement BlockingCollection to fix this Producer/Consumer issue? C#BlockingCollection生成者使用者而不阻塞使用者线程 - C# BlockingCollection producer consumer without blocking consumer thread 使用阻塞收集和任务的经典生产者消费者模式 .net 4 TPL - classic producer consumer pattern using blockingcollection and tasks .net 4 TPL 使用 BlockingCollection 通过生产者-消费者模式保存图像 - Saving images via producer-consumer pattern using BlockingCollection 生产者 - 消费者在队列为空时等待? - Producer-Consumer waiting when queue is empty?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM