C#中的生产者/混合使用者使用4.0框架类和阻止集合

时间:2015-05-20 19:45:43

标签: c# multithreading c#-4.0 concurrency producer-consumer

我的情况是我有生产者/消费者情景。制作人永远不会停止,这意味着即使有时间BC中没有项目,也可以在以后添加更多项目。

从.NET Framework 3.5迁移到4.0,我决定使用BlockingCollection作为使用者和生产者之间的并发队列。我甚至添加了一些并行扩展,因此我可以将BC与Parallel.ForEach一起使用。

问题在于,在消费者线程中,我需要一种混合模型:

  1. 我总是检查BC以处理任何到达的项目 Parallel.ForEach(bc.GetConsumingEnumerable(), item => etc
  2. 在这个foreach中,我执行彼此之间不相互依赖的所有任务。
  3. 问题出现了。在对前面的任务进行并行化之后,我需要按照它们在BC中的相同FIFO顺序来管理它们的结果。这些结果的处理应该在同步线程中进行。
  4. 伪代码中的一个小例子如下:

    生产者:

    //This event is triggered each time a page is scanned. Any batch of new pages can be added at any time at the scanner
    private void Current_OnPageScanned(object sender, ScannedPage scannedPage)
    {          
         //The object to add has a property with the sequence number
        _concurrentCollection.TryAdd(scannedPage);
    }
    

    消费者:

    private void Init()
    {
        _cancelTasks = false;
        _checkTask = Task.Factory.StartNew(() =>
                {
                    while (!_cancelTasks)
                    {
                        //BlockingCollections with Paralell ForEach
                        var bc = _concurrentCollection;
                        Parallel.ForEach(bc.GetConsumingEnumerable(), item =>
                        {
                            ScannedPage currentPage = item;
                            // process a batch of images from the bc and check if an image has a valid barcode. T
                        });
                        //Here should go the code that takes the results from each tasks, process them in the same FIFO order in which they entered the BC and save each image to a file, all of this in this same thread.
    
                    }
                });
    }
    

    显然,这不能正常工作,因为.GetConsumingEnumerable()阻止,直到BC中有另一个项目。我认为我可以完成任务,只需在同一批次中激活4或5个任务,但是:

    1. 我怎么能用任务完成这个任务,并且在任务开始之前仍然有一个等待点,直到BC中有一个要消耗的项目为止(如果什么都没有,我不想开始处理。在BC中有一些东西我会开始批量的4个任务,并在每个任务中使用TryTake所以如果没有什么可以采取它们不会阻止,因为我不知道我是否可以永远达到来自BC的项目数量作为一批任务,例如,BC中剩下的一个项目和一批4个任务)?
    2. 我怎么能这样做并利用Parallel.For提供的效率?
    3. 如何以从BC中提取项目的相同FIFO顺序保存任务结果?
    4. 是否还有其他并发类更适合消费者对这类项目的混合处理?
    5. 另外,这是我在StackOverflow中提出的第一个问题,所以如果您需要更多数据,或者您认为我的问题不正确,请告诉我。

1 个答案:

答案 0 :(得分:2)

我想我会按照您的要求进行操作,为什么不创建一个ConcurrentBag并在处理时加入它:

while (!_cancelTasks)
{
   //BlockingCollections with Paralell ForEach
   var bc = _concurrentCollection;
   var q = new ConcurrentBag<ScannedPage>();
   Parallel.ForEach(bc.GetConsumingEnumerable(), item =>
   {
      ScannedPage currentPage = item;
      q.Add(item);
      // process a batch of images from the bc and check if an image has a valid barcode. T
   });
 //Here should go the code that takes the results from each tasks, process them in the same FIFO order in which they entered the BC and save each image to a file, all of this in this same thread.


  //process items in your list here by sorting using some sequence key
  var items = q.OrderBy( o=> o.SeqNbr).ToList();
  foreach( var item in items){
     ...
  }
}

这显然不会按照它们添加到BC的确切顺序排列它们,但是您可以像Alex建议的那样向ScannedPage对象添加一些序列nbr,然后对结果进行排序。

以下是我处理序列的方法:

将其添加到ScannedPage类:

public static int _counter;  //public because this is just an example but it would work.

获取序列nbr并在此处分配:

private void Current_OnPageScanned(object sender, ScannedPage scannedPage)
{          
    lock( this){   //to single thread this process.. not necessary if it's already single threaded of course.
    System.Threading.Interlocked.Increment( ref ScannedPage._counter);
    scannedPage.SeqNbr = ScannedPage._counter;
    ...
    }
}