Monitor.Wait需要同步吗?

我已经开发了一个通用的生产者 – 消费者队列,它由Monitor以下列方式产生脉冲:

入队:

public void EnqueueTask(T task) { _workerQueue.Enqueue(task); Monitor.Pulse(_locker); } 

出队:

 private T Dequeue() { T dequeueItem; if (_workerQueue.Count > 0) { _workerQueue.TryDequeue(out dequeueItem); if(dequeueItem!=null) return dequeueItem; } while (_workerQueue.Count == 0) { Monitor.Wait(_locker); } _workerQueue.TryDequeue(out dequeueItem); return dequeueItem; } 

wait部分产生以下SynchronizationLockException:“从非同步代码块调用对象同步方法”我是否需要同步它? 为什么? 使用ManualResetEvents还是Slim版本的.NET 4.0会更好吗?

是的,当前线程需要“拥有”监视器以便调用WaitPulse ,如文档所述。 (所以你也需要锁定Pulse 。)我不知道为什么需要它的细节,但它在Java中是相同的。 我通常会发现,无论如何我都想这样做,以使调用代码干净。

请注意, Wait会释放监视器本身,然后等待Pulse ,然后在返回之前重新获取监视器。

至于使用ManualResetEventAutoResetEvent – 你可以,但我个人更喜欢使用Monitor方法,除非我需要等待句柄的其他一些function(例如自动等待任何/所有多个句柄)。

从Monitor.Wait()的MSDN描述:

释放对象的锁定并阻止当前线程,直到它重新获取锁定。

“释放锁定”部分是问题,对象未锁定。 您正在将_locker对象视为WaitHandle。 做一个可certificate是正确的锁定设计是一种黑魔法,最好留给我们的医学家杰弗里里希特和乔达菲。 但我会给这一个:

 public class BlockingQueue { private Queue queue = new Queue(); public void Enqueue(T obj) { lock (queue) { queue.Enqueue(obj); Monitor.Pulse(queue); } } public T Dequeue() { T obj; lock (queue) { while (queue.Count == 0) { Monitor.Wait(queue); } obj = queue.Dequeue(); } return obj; } } 

在大多数任何实际的生产者/消费者场景中,您都希望限制生产者,使其无法填充队列无限制。 查看Duffy的BoundedBuffer设计示例。 如果你有能力转向.NET 4.0那么你肯定想要利用它的ConcurrentQueue类,它有更多的黑魔法,低开销锁定和旋转等待。

查看Monitor.WaitMonitor.Pulse / PulseAll的正确方法并不是提供等待的方法,而是( Wait )作为让系统知道代码处于等待循环但无法退出的方法直到感兴趣的东西发生变化,并且(对于Pulse / PulseAll )作为一种让系统知道代码刚刚改变的东西,可能导致某些其他线程的等待循环满足退出条件。 应该能够替换所有出现的Wait with Sleep(0)并且仍然能够正常工作(即使效率低得多,因为花费CPU时间反复测试未改变的条件)。

要使此机制起作用,必须避免以下顺序的可能性:

  • 等待循环中的代码在不满足时测试条件。

  • 另一个线程中的代码更改条件以使其满足。

  • 其他线程中的代码会发出锁定(没有人等待)。

  • 等待循环中的代码执行Wait因为它的条件不满足。

Wait方法要求等待线程有一个锁,因为这是确保它所等待的条件在它测试的时间和代码执行Wait之间不会改变的唯一方法。 Pulse方法需要锁定,因为这是唯一可以确定如果另一个线程已经“提交”自己执行Wait ,则在其他线程实际执行之前不会发生Pulse 。 请注意,在锁定中使用Wait并不能保证它的使用正确,但是在锁定之外使用Wait可能是正确的。

如果双方合作, Wait / Pulse设计实际上工作得相当好。 设计的最大弱点,恕我直言,是(1)没有机制让线程等到任何一个物体被脉动; (2)即使一个人正在“关闭”一个对象,以便所有未来的等待循环都应立即退出(可能通过检查退出标志),确保线程已经提交的任何Wait的唯一方法将获得一个Pulse是获得锁定,可能无限期地等待它变得可用。