2010-09-26 93 views
6

我已经开发出脉冲信号,通过监视器以下列方式通用的生产者 - 消费者队列:Monitor.Wait是否需要同步?

入队:

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; 
    } 

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

回答

6

是的,当前的线程需要“拥有”显示器以调用WaitPulse,如记录。 (所以你也需要锁定Pulse。)我不知道为什么它是必需的细节,但它在Java中是一样的。尽管如此,我通常会发现我想要这样做,以使调用代码清洁。

请注意,Wait会自行释放显示器,然后等待Pulse,然后在返回之前重新获取显示器。

至于使用ManualResetEventAutoResetEvent而是 - 你可以,但我个人更喜欢使用Monitor方法,除非我需要一些等待句柄的其他功能(如原子等待任何/所有的多手柄)。

+0

你为什么要这样做?你将如何合并显示器?只是用于显示器的锁定器对象的锁定?不知道锁是否添加了ResetEvents不需要的另一个上下文切换? – user437631 2010-09-26 13:14:00

+0

@ user437631:是的,只是一个普通的'lock'语句没问题。这可能需要或可能不需要额外的上下文切换 - 我不认为您有任何证据表明ResetEvents不需要它。实际上,由于它们是CLR内部对象,而不是可能交叉处理Win32对象,所以监视器比ResetEvents更轻。 – 2010-09-26 15:46:24

2

从Monitor.Wait()的描述MSDN:

释放锁物体上,并阻塞当前线程,直到它重新获取锁。

'释放锁'部分是问题,对象没有锁定。您正在将_locker对象视为WaitHandle。做你自己的锁定设计,证明是正确的,这是一种黑魔法,最好留给我们的医生,杰弗里里克特和乔达菲。但是我给这一个镜头:

public class BlockingQueue<T> { 
    private Queue<T> queue = new Queue<T>(); 

    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; 
    } 
} 

在大多数你将要节流生产,因此它不能填补队列无界任何实际生产者/消费者方案。以达菲的BoundedBuffer design为例。如果你可以负担得起.NET 4.0,那么你肯定会利用它的ConcurrentQueue类,它具有更多的黑魔法,低开销锁定和等待旋转。

0

,以查看Monitor.WaitMonitor.Pulse/PulseAll并不像让系统的手段提供的等待的手段,而是(为Wait)的正确方式知道该代码是在等待循环,其不能退出直到感兴趣的东西发生变化,并且(对于Pulse/PulseAll)作为让系统知道代码刚刚改变了某些可能导致满足退出条件的某个其他线程的等待循环的一种手段。一个应该能够用Sleep(0)代替Wait的所有事件,并且仍然具有正确的代码工作(即使花费CPU时间重复测试没有改变的条件的效率要低得多)。

对于这种工作机制,就必须避免以下序列的可能性:

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

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

  • 该另一个线程中的代码激发锁定(没有人还在等待)。

  • 由于条件不满足,等待循环中的代码执行Wait

Wait方法需要等待的线程有锁,因为这是它可以确保它在等待时病情会不会它的测试时间和代码执行的时间之间改变的唯一途径WaitPulse方法需要一个锁,因为这是唯一的方法,它可以确保如果另一个线程已“提交”自己执行WaitPulse将不会发生,直到其他线程实际上这样做。请注意,在锁内使用Wait并不能保证它被正确使用,但在锁外使用Wait可能是正确的。

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