Monitor.Wait нужна синхронизация? - PullRequest
7 голосов
/ 26 сентября 2010

Я разработал общую очередь производитель-потребитель, которая посылает 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;
    }

Секция ожидания создает следующее SynchronizationLockException: «Метод синхронизации объекта был вызван из несинхронизированного блока кода». Нужно ли мне его синхронизировать?Зачем ?Лучше использовать ManualResetEvents или Slim версию .NET 4.0?

Ответы [ 3 ]

6 голосов
/ 26 сентября 2010

Да, текущему потоку необходимо «владеть» монитором, чтобы вызвать либо Wait, либо Pulse, как задокументировано. (Так что вам также нужно заблокировать Pulse.) Я не знаю подробностей, почему это требуется, но в Java то же самое. Я обычно обнаруживал, что в любом случае хочу сделать это, чтобы сделать вызывающий код чистым.

Обратите внимание, что Wait освобождает сам монитор, затем ждет Pulse и затем возвращает монитор перед возвратом.

Что касается использования ManualResetEvent или AutoResetEvent вместо этого - вы могли бы, но лично я предпочитаю использовать методы Monitor, если мне не нужны некоторые другие функции дескрипторов ожидания (такие как атомарное ожидание любого / всего множества ручки).

2 голосов
/ 26 сентября 2010

Из описания MSDN Monitor.Wait ():

Снимает блокировку с объекта и блокирует текущий поток, пока он не получит блокировку повторно.

Проблема «освобождает блокировку», объект не заблокирован. Вы рассматриваете объект _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 Даффи для примера. Если вы можете позволить себе перейти на .NET 4.0, тогда вы определенно хотите воспользоваться его классом ConcurrentQueue, в нем гораздо больше черной магии с блокировкой с минимальными издержками и ожиданием вращения.

0 голосов
/ 08 мая 2013

Правильный способ просмотра Monitor.Wait и Monitor.Pulse / PulseAll - это не средство ожидания, а (для Wait) средство информирования системы о том, что код находится в цикл ожидания, который не может выйти до тех пор, пока что-то интересное не изменится, и (для Pulse / PulseAll) как средство оповещения системы о том, что код только что изменил что-то, что может вызвать удовлетворение условия выхода цикла ожидания другого потока , Нужно иметь возможность заменить все вхождения Wait на Sleep(0) и все равно иметь код, работающий корректно (даже если гораздо менее эффективно, из-за того, что тратит процессорное время многократно тестируя условия, которые не изменились).

Чтобы этот механизм работал, необходимо избегать возможности следующей последовательности:

  • Код в цикле ожидания проверяет условие, когда оно не выполняется.

  • Код в другом потоке изменяет условие, так что оно выполняется.

  • Код в этом другом потоке запускает блокировку (которую никто еще не ждет).

  • Код в цикле ожидания выполняет Wait, поскольку его условие не было выполнено.

Метод Wait требует, чтобы ожидающий поток имел блокировку, поскольку это единственный способ убедиться, что ожидаемое условие не изменится между временем его тестирования и временем выполнения кода * 1033. *. Для метода Pulse требуется блокировка, потому что это единственный способ убедиться, что если другой поток «обязался» выполнить Wait, Pulse не будет происходить до тех пор, пока другой поток фактически не сделает этого. Обратите внимание, что использование Wait в пределах блокировки не гарантирует его правильного использования, но невозможно использовать Wait вне блокировки.

Дизайн Wait / Pulse действительно работает достаточно хорошо, если обе стороны сотрудничают. ИМХО, наибольшие слабые стороны проекта: (1) нет никакого механизма для потока, ожидающего, пока какой-либо из объектов не будет импульсным; (2) даже если кто-то «закрывает» объект таким образом, что все будущие циклы ожидания должны выходить немедленно (возможно, путем проверки флага выхода), единственный способ гарантировать, что любой Wait, к которому потянул поток, получит Pulse - получить замок, возможно, ожидая, пока он станет доступным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...