C#中的队列和等待句柄

时间:2010-04-29 21:54:09

标签: c# .net multithreading queue autoresetevent

我的应用程序中已经有以下代码多年,并且从未见过它的问题。

while ((PendingOrders.Count > 0) || (WaitHandle.WaitAny(CommandEventArr) != 1))
{
    lock (PendingOrders)
    {
       if (PendingOrders.Count > 0)
       {
           fbo = PendingOrders.Dequeue();
       }
       else
       {
           fbo = null;
       }
    }

    // Do Some Work if fbo is != null
}

其中CommandEventArr由NewOrderEvent(自动重置事件)和ExitEvent(手动重置事件)组成。

但是我不确定这是否是线程安全的(假设N个生成器线程在排队之前全部锁定队列,并且一个消费者线程运行上面的代码)。此外,我们可以假设Queue.Count属性只从Queue类返回一个实例Int32值(没有volatile或者互锁或锁定等)。

Queue和AutoResetEvent用于修复此问题的常用模式是什么,并且我正在尝试使用上面的代码做什么?

(编辑后,在正确指出Queue.Count可以执行任何操作及其特定实现之后稍微更改问题。)

4 个答案:

答案 0 :(得分:4)

对我来说看起来非常线程安全,WaitAny()将立即完成,因为已经设置了事件。那是问题。

不要破坏有效的线程同步。但是如果你想要一个更好的捕鼠器,你可以在这个magazine article中考虑Joe Duffy的BlockingQueue。它的更通用版本在.NET 4.0中可用,System.Collections.Concurrent.BlockingCollection使用ConcurrentQueue作为它的实际实现。

答案 1 :(得分:3)

你是对的。代码不是线程安全的。但不是你想的原因。

AutoResetEvent很好。虽然,只是因为你获得了一个锁并重新测试PendingOrders.Count。问题的真正症结在于您在锁定之外调用PendingOrders.Count。因为Queue类不是线程安全的,所以你的代码不是线程安全的...句号。

现在实际上你可能永远不会有这个问题有两个原因。首先,Queue.Count属性几乎肯定是为了永远不会让对象处于半生不熟的状态。毕竟,它可能只返回一个实例变量。其次,在读取时缺少内存屏障不会对代码的更广泛上下文产生重大影响。最糟糕的事情是你将在循环的一次迭代中得到一个陈旧的读取,然后获得的锁将隐式地创建一个内存屏障,并在下一次迭代时发生新的读取。我在这里假设只有一个线程排队项目。如果有2个或更多,情况会发生很大变化。

但是,让我非常清楚。 您无法保证PendingOrders.Count在执行期间不会改变对象的状态。并且因为它没有包含在锁中,所以另一个线程可以在它处于半支持状态时启动它的操作。

答案 2 :(得分:2)

使用手动活动......

ManualResetEvent[] CommandEventArr = new ManualResetEvent[] { NewOrderEvent, ExitEvent };

while ((WaitHandle.WaitAny(CommandEventArr) != 1))
{
    lock (PendingOrders)
    {
        if (PendingOrders.Count > 0)
        {
            fbo = PendingOrders.Dequeue();
        }
        else
        {
            fbo = null;
            NewOrderEvent.Reset();
        }
    }
}

然后你需要确保在入队方面锁定:

    lock (PendingOrders)
    {
        PendingOrders.Enqueue(obj);
        NewOrderEvent.Set();
    }

答案 3 :(得分:0)

您应该只使用WaitAny,并确保在添加到PendingOrders集合的每个新订单上发出信号:

while (WaitHandle.WaitAny(CommandEventArr) != 1))
{
   lock (PendingOrders)
   {
      if (PendingOrders.Count > 0)
      {
          fbo = PendingOrders.Dequeue();
      }
      else
      {
          fbo = null;

          //Only if you want to exit when there are no more PendingOrders
          return;
      }
   }

   // Do Some Work if fbo is != null
}