c# – 如何在BlockingCollection实例上进行可中断的等待?

简单的场景 – 我想阻止BlockingCollection< T> (如果它是空的).另一方面,我想阻塞一个ManualResetEvent对象,用于在需要终止线程时发出信号.

我知道没有办法使用WaitHandle.WaitAny,因为阻塞BlockingCollection< T>同时返回一个项目,并且不适合WaitHandle.WaitAny规定的API.

我能想到的最简单的方法是阻止BlockingCollection< T>使用超时然后在等待句柄上等待0超时,如下所示:

ManualResetEvent term = ...;
BlockingCollection<T> coll = ...;
while (true)
{
  T obj;
  bool found = coll.TryTake(out obj, 500);
  if (term.WaitOne(0))
  {
    break;
  }
  if (found)
  {
    // process the obj
  }
}

但我觉得应该有一个更优雅的解决方案,可能使用其他东西而不是BlockingCollection< T>?

欢迎任何想法.

编辑1

我可以从MRE转到使用取消令牌,如果需要更好的解决方案.

最佳答案 最简单的选择是使用CancellationToken(通过CancellationTokenSource发出信号)而不是结合GetConsumingEnumerable的ManualResetEvent(在评论中建议):

var blockingCollection = ...
foreach (var obj in blockingCollection.GetConsumingEnumerable(cancellationToken))
{
    // process the obj
}

允许使用类似于ManualResetEvent的构造并且在不浪费线程的情况下异步“阻塞”的另一个选项是使用TPL Dataflow的BufferBlock和AsyncManualResetEvent:

AsyncManualResetEvent term = ...
BufferBlock<T> buffer = ...
var termTask = term.WaitAsync();
while (true)
{
    var receiveTask = buffer.ReceiveAsync();
    if (termTask == await Task.WhenAny(receiveTask, termTask))
    {
        break;
    }

    T obj = await receiveTask;
    // process the obj
}

您可以根据Stephen Toub的编写自己的AsyncManualResetEvent:Building Async Coordination Primitives, Part 1: AsyncManualResetEvent或使用Visual Studio SDK中的一个:AsyncManualResetEvent

点赞