如何优雅地访问线程安全的集合并使用AutoResetEvent

时间:2010-11-10 17:05:46

标签: c# multithreading

我有两种方法,ProcessQueueAddToQueue,它们发生在不同的线程上。有时我会在项目添加到队列之前尝试处理队列,此时我想等待项目添加到队列中。我还想确保我永远不会遇到我等待的情况,之后,队列被评估为空,然后之后将队列添加到另一个队列中线。下面是我尝试这样做,但是由于自动重置事件在锁定仍然有效的情况下等待,因此会产生死锁。

必须有一种更优雅的方式来做到这一点。有什么建议吗?

private readonly object m_Locker = new object();
private readonly Queue<int> m_Queue = new Queue<int>();
private readonly AutoResetEvent m_AutoResetEvent = new AutoResetEvent(false);

void ProcessQueue()
{
  lock (m_Locker)
  {
    if (m_Queue.Count == 0)
    {
      // nothing is happening, so wait for it to happen
      m_AutoResetEvent.WaitOne();
    }
  }
  Console.WriteLine("Processed {0}", m_Queue.Dequeue());
}

// on another thread
void AddToQueue(int i)
{
  lock (m_Locker) 
  {
    m_Queue.Enqueue(i);
    m_AutoResetEvent.Set();
  }
}

6 个答案:

答案 0 :(得分:3)

在发出等待之前,您必须释放队列m_locker上的锁。您可以使用Monitor手动执行此操作,在等待满足后重新获取并重新检查。这样,只有在检查非零元素计数时才能保持锁定。

如果您使用.Net 4,则可以使用BlockingCollection<T>ConcurrentQueue<T>来代替System.Collections.Concurrent。实际上没有理由再手工制作这个。

如果您有&gt;此代码将无法使用1个并发消费者 - 在这种情况下,您需要Semaphore而不是AutoResetEvent,以确保获得正确数量的消费者信号。

由于您无法使用.Net 4,因此针对此方案here提供了指导原则。请注意,该文章的评论包括一些可用于实现此防弹的方法。

  

以下示例演示   线程之间的同步   主线程和两个工作线程   使用lock关键字,和   AutoResetEvent和ManualResetEvent   类。

答案 1 :(得分:2)

问题是您在等待活动时锁定队列。

这样,其他进程无法添加到队列中,因为它已被锁定。试试这个:

int value = 0;

while (true)
{
    lock (m_Locker)
    {
        if (m_Queue.Count > 0)
        {
            value = m_Queue.Dequeue();
            break;
        }
    }

    m_AutoResetEvent.WaitOne();
}

通过上面的示例,您还会在锁中出列,因此您确定没有其他线程有机会在您等待的时刻和检查队列实际上有项目之间出列队列。

答案 2 :(得分:1)

嗯,这是教科书的死锁示例。最重要的是,您不希望在锁定ProcessQueue函数中的m_locker时在AutoResetEvent上进入等待状态。

另请注意,.NET中的通用队列实现不是线程安全的,因此您还应该保护对DequeueProcessQueue调用的访问权限。

答案 3 :(得分:1)

你不想这样做:

// no lock up here
while (true)
{
  // nothing is happening, so wait for it to happen
  m_AutoResetEvent.WaitOne();

  lock (m_locker)  
  {
  // ProcessTheQueue(); // process the queue after the reset event is Set 
  }
}

然后:

lock (m_Locker) 
  {
    m_Queue.Enqueue(i);
  }
    m_AutoResetEvent.Set();

答案 4 :(得分:1)

如果您使用的是.NET 4,则新BlockingCollection<T>提供了处理此问题的最佳方式。

答案 5 :(得分:0)

为什么首先要使用AutoResetEvent?

当你调用Process函数时,如果找不到它应该退出的东西。我没有看到等待的重点,因为你可能会在一段时间之后再次调用它......