Why does iterating over GetConsumingEnumerable() not fully empty the underlying blocking collection

前端 未结 4 1937
天涯浪人
天涯浪人 2021-02-04 04:16

I have a quantifiable & repeatable problem using the Task Parallel Library, BlockingCollection, ConcurrentQueue & GetCo

相关标签:
4条回答
  • 2021-02-04 04:35

    I couldn't replicate your behavior with simple console application doing basically the same thing (running on .Net 4.5 beta, which could make a difference). But I think the reason this happens is that Parallel.ForEach() tries to optimize execution by splitting the input collection into chunks. And with your enumerable, a chunk can't be created until you add more items to the collection. For more information, see Custom Partitioners for PLINQ and TPL on MSDN.

    To fix this, don't use Parallel.ForEach(). If you still want to process the items in parallel, you can start a Task in each iteration.

    0 讨论(0)
  • 2021-02-04 04:35

    I feel like I should note just for clarity that in instances where you are able to call the BlockingCollection's .CompleteAdding() method prior to executing the Parallel.foreach, the issue you describe above will not be a problem. I have used these two objects together many times with great results.

    In addition, you can always re-set your BlockingCollection after calling CompleteAdding() to add more items when needed (_entries = new BlockingCollection();)

    Changing the click event code above as follows would solve your problem with the missing entry and make it work as expected, if you click the start and stop buttons multiple times:

    private void button2_Click(object sender, EventArgs e)
    { //STOP BUTTON
        timer1.Stop();
        timer1.Enabled = false;
    >>>>_entries.CompleteAdding();
    >>>>_entries = new BlockingCollection<int>();
    }
    
    0 讨论(0)
  • 2021-02-04 04:49

    As of .net 4.5, you can create a partitioner which will take only 1 item at a time:

    var partitioner = Partitioner.Create(jobsBatchesQ.queue.GetConsumingEnumerable(), EnumerablePartitionerOptions.NoBuffering);
    Parallel.ForEach(partitioner, new ParallelOptions { MaxDegreeOfParallelism = (currentTask.ParallelLevel > 0 ? currentTask.ParallelLevel : 1) }, (batch, state) => {//do stuff}
    

    https://msdn.microsoft.com/en-us/library/system.collections.concurrent.enumerablepartitioneroptions(v=vs.110).aspx

    0 讨论(0)
  • 2021-02-04 05:01

    You can't use GetConsumingEnumerable() in Parallel.ForEach().

    Use the GetConsumingPartitioner from the TPL extras

    In the blog post you will also get an explanation why can't use GetConsumingEnumerable()

    The partitioning algorithm employed by default by both Parallel.ForEach and PLINQ use chunking in order to minimize synchronization costs: rather than taking the lock once per element, it'll take the lock, grab a group of elements (a chunk), and then release the lock.

    i.e. Parallel.ForEach wait until it receives a group of work items before continuing. Exactly what your experiment shows.

    0 讨论(0)
提交回复
热议问题