Вопрос

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

Это было полезно?

Решение

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

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

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

Другие советы

Из описания 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;
    }
}

В большинстве практических сценариев производитель/потребитель вам потребуется ограничить производитель, чтобы он не мог неограниченно заполнять очередь.Проверьте Даффи Дизайн ограниченного буфера для примера.Если вы можете позволить себе перейти на .NET 4.0, то вам определенно захочется воспользоваться преимуществами его класса ConcurrentQueue, в нем гораздо больше черной магии с блокировкой с низкими издержками и ожиданием вращения.

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

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

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

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

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

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

То Wait Метод требует, чтобы в ожидании потока есть замок, поскольку это единственный способ, которым можно быть уверены, что условие, которое он ждет, не изменяется между временем, когда он проверяется и время, когда код выполняет Wait. Отказ То Pulse Метод требует блокировки, потому что это единственный способ быть уверенным, что если другая нить «совершила» сама для выполнения Wait, то Pulse Не произойдет до тех пор, пока не на самом деле не так. Обратите внимание, что использование Wait В рамках блокировки не гарантируют, что он используется правильно, но не так, чтобы использовать Wait вне замка может быть правильно.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top