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

C#中的生产者/混合消费者使用4.0框架类和阻塞集合,c#,multithreading,c#-4.0,concurrency,producer-consumer,C#,Multithreading,C# 4.0,Concurrency,Producer Consumer,我有一个生产者/消费者场景。制作人从不停止,这意味着即使有一段时间BC中没有项目,以后也可以添加更多的项目 从.NETFramework3.5到4.0,我决定使用BlockingCollection作为消费者和生产者之间的并发队列。我甚至添加了一些并行扩展,这样我就可以将BC与parallel.ForEach一起使用 问题是,在消费者线程中,我需要一种混合模型: 我总是检查业务连续性,以处理任何带有 Parallel.ForEach(bc.getconsumineGenumerable(),it

我有一个生产者/消费者场景。制作人从不停止,这意味着即使有一段时间BC中没有项目,以后也可以添加更多的项目

从.NETFramework3.5到4.0,我决定使用
BlockingCollection
作为消费者和生产者之间的并发队列。我甚至添加了一些并行扩展,这样我就可以将BC与
parallel.ForEach
一起使用

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

  • 我总是检查业务连续性,以处理任何带有
    Parallel.ForEach(bc.getconsumineGenumerable(),item=>etc
  • 在这个
    foreach
    中,我执行彼此之间不依赖的所有任务
  • 问题来了。在并行化之前的任务后,我需要按照它们在BC中的相同FIFO顺序管理它们的结果。这些结果的处理应该在同步线程中进行
  • 下面是伪代码中的一个小示例:

    制作人:

    //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.
    
                    }
                });
    }
    
    显然,这无法正常工作,因为
    .getconsumineGenumerable()
    会一直阻塞,直到BC中有另一个项目。我想我可以用任务来完成,只需在同一批中启动4或5个任务,但是:

  • 在任务开始之前,我如何处理任务,并且仍然有一个等待点,阻止任务开始,直到BC中有一个要消费的项目(我不想在什么都没有的情况下开始处理。一旦BC中有了一些东西,我就开始4个任务的批处理,并在每个任务中使用
    TryTake
    ,这样如果没有什么东西可以拿,它们就不会阻塞,因为我不知道我是否总能达到BC中作为任务批处理的项目数,例如,只有一个任务我留在BC和一批4个任务中)
  • 我如何才能做到这一点,并利用并行的效率。为提供
  • 如何以从BC中提取项目的相同FIFO顺序保存任务的结果
  • 是否有其他并发类更适合于消费者中项目的这种混合处理
  • 此外,这是我在StackOverflow中提出的第一个问题,所以如果您需要更多数据,或者您认为我的问题不正确,请告诉我

  • 我想我会按照您的要求来做,为什么不创建一个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){
         ...
      }
    }
    
    获取序列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;
        ...
        }
    }
    

    您的生产者代码段缺少一个paren,它也没有任何意义。有几件事:(1)使用
    CancelationToken
    而不是布尔标志来执行取消操作。(2)除非取消操作一直持续,否则您将何时处理一系列结果?(3)这些结果是什么?(4)您需要让制作人确定与enqueue上的
    ScannedPage
    关联的序列号,或者
    ScannedPage
    实例必须有序列号。您好,Alex。回应您提到的内容。1)谢谢!2)我有什么选择,这样它就不会永远继续,而是在每次检测到BC不是空的时候,以n个元素为一批处理项目?3) 我按顺序接收一些扫描的页面,在并行部分,我对每个图像执行独立的处理(读取条形码、旋转条形码等等)。在这部分之后,我需要按照图像的顺序保存图像(因此需要FIFO),因为保存图像的路径可能取决于之前的图像。4) 每个项目都有序列号。感谢您的快速回答。关于你的回答,我有几个问题:1)这能保证我的FIFO顺序吗(我的意思是,它会按照foreach的顺序排列)?2) 在我添加到队列后,我正在更改项目(在处理部分),这不会有问题吗?因为您正在并行化循环,因为顺序不保证,所以您需要在对象上使用一些标记来指示序列。我将编辑另一种线程安全方式,假设ScannedPage有一个
    SeqNbr
    属性,您在前面设置了该属性来指示它的顺序。为什么一个包比一个BlockingCollection(队列)更好?除了BC之外,它对生产者/消费者没有什么不同,我只需要一个简单的线程安全列表。如果你愿意,它将与BC一起工作。嗨,谢谢!!!!!,我会的。现在是问题的另一部分。我有什么选择,使它不会永远继续(在Parallel.ForEach中,或在没有元素时阻塞),而是在每次检测到BC不是空的时候,以n个元素为一批处理项目?在BC中是否有某种Peek()方法会一直阻塞,直到其中有一个元素?
    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;
        ...
        }
    }