C#,BlockingCollection:如何等到收集少于N个项目

时间:2011-04-14 06:43:09

标签: c# task-parallel-library

大家。 我在传统的生产者 - 消费者场景中使用BlockingCollection。要逐个处理集合中的项目,我必须编写此代码:

while (...)
{
  var item = collection.Take(cancellationTokenSource.Token);
  ProcessItem(item);
}

但是如何处理一批N个项目(等到收集少于N个项目)? 我的解决方案是使用一些临时缓冲区:

var buffer = new List<MyType>(N);

while (...)
{
  var item = collection.Take(cancellationTokenSource.Token);

  buffer.Add(item);
  if (buffer.Count == N)
  {
    foreach (var item in items)
    {
      ProcessItem(item);
    }

    buffer.Clear();
  }
}

但在我看来非常难看......有没有更好的方法?

[UPDATE]: 这是扩展方法的原型,使解决方案更具可读性。也许,有人会发现它很有用:

public static class BlockingCollectionExtensions
{
    public static IEnumerable<T> TakeBuffer<T>(this BlockingCollection<T> collection,
        CancellationToken cancellationToken, Int32 bufferSize)
    {
        var buffer = new List<T>(bufferSize);

        while (buffer.Count < bufferSize)
        {
            try
            {
                buffer.Add(collection.Take(cancellationToken));
            }
            catch (OperationCanceledException)
            {
                // we need to handle the rest of buffer,
                // even if the task has been cancelled.
                break;
            }
        }

        return buffer;
    }
}

用法:

foreach (var item in collection.TakeBuffer(cancellationTokenSource.Token, 5))
{
  // TODO: process items here...
}

当然,这不是一个完整的解决方案:例如,我会添加任何超时支持 - 如果没有足够的项目,但是时间已经过去,我们需要停止等待并处理已添加到缓冲区的项目。

3 个答案:

答案 0 :(得分:0)

我找不到那种丑陋的解决方案。批处理是阻塞集合的正交要求,应该如此处理。我会将批处理行为封装在一个带有干净接口的BatchProcessor类中,但除此之外,我并没有真正看到该方法存在问题。

答案 1 :(得分:0)

您可能会发现队列的无锁实现以及阻塞集合是不成熟的优化。如果您退后一步并使用基于监视器的锁定队列,则可以编写更干净的代码。

答案 2 :(得分:0)

首先,我不确定你的逻辑是否正确。你说你要等到收集少于N个项目 - 不是相反吗?您希望集合包含N个或更多项目,以便处理N个项目。或者也许是我的误解。

然后我还建议您在项目少于N个项目时逐个处理项目,或者您可能会发现您的应用程序似乎挂在N-1项目上。当然,如果这是一个稳定的数据流,只有当buffer.Count&gt; = N时处理才足够好。

我建议去排队,像GregC这样的监视器。

这样的事情:

public object Dequeue() {   
  while (_queue.Count < N)   { 
    Monitor.Wait(_queue);   
  } 
 return _queue.Dequeue();
}

public void Enqueue( object q )
{
  lock (_queue)
 {
  _queue.Enqueue(q);
  if (_queue.Count == N)
  {
      // wake up any blocked dequeue call(s)
      Monitor.PulseAll(_queue);
  }
 }
}