Monitor.Wait需要同步吗?

时间:2010-09-26 13:02:17

标签: c# synchronization monitor producer-consumer

我开发了一个通用的生产者 - 消费者队列,它按以下方式由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更好?

3 个答案:

答案 0 :(得分:6)

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

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

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

答案 1 :(得分: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;
    }
}

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

答案 2 :(得分:0)

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

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

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

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

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

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

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

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