如何对BlockingCollection <t>实例进行可中断的等待?</t>

时间:2014-09-02 15:39:41

标签: c# .net multithreading concurrency

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

我了解无法使用WaitHandle.WaitAny,因为BlockingCollection<T>上的阻止会同时返回某个项目,并且不适合WaitHandle.WaitAny指示的API。< / p>

我能想到的最简单的方法是使用超时阻塞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转移到使用取消令牌,如果能够提供更优雅的解决方案。

2 个答案:

答案 0 :(得分:3)

最简单的选择是使用CancellationToken(通过CancellationTokenSource发出信号)而不是ManualResetEvent(正如评论中建议的那样)与GetConsumingEnumerable结合使用:

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

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

BufferBlock

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

答案 1 :(得分:0)

CancellationTokenSource就是您所需要的;将其插入控制台应用程序并调用它。

private static void DoSomeWork()
{
    BlockingCollection<int> coll = new BlockingCollection<int>();
    CancellationTokenSource source = new CancellationTokenSource();
    ThreadPool.QueueUserWorkItem((s) =>
    {
        Console.WriteLine("Thread started. Waiting for item or cancellation.");
        try
        {
            var x = coll.Take(source.Token);
            Console.WriteLine("Take completed.");
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Take cancelled. IsCancellationRequested={0}", source.IsCancellationRequested);
        }
    });
    Console.WriteLine("Press ENTER to cancel wait.");
    Console.ReadLine();
    source.Cancel(false);
    Console.WriteLine("Cancel sent. Press Enter to exit.");
    Console.ReadLine();
}