简体   繁体   中英

Producer/Consumer, BlockingCollection, and waiting for changes

I'm trying to wrap my head around BlockingCollection and my producer/consumer problem.

What I want to achieve, is the following:

  • A thread-safe queue of sorts to hold a list of objects ("jobs") in FIFO manner.
  • A second thread-safe queue, which holds a list of results of those jobs, also in FIFO manner.

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. I'm not sure if foreach is the correct approach for that.

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. I'm not sure if foreach is the correct approach for that.

This is right approach, foreach will be blocked until new item will be added to the queue or CompleteAdding method will be called. Not right is that you want achieve asynchronous processing with 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. 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. 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. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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