Очереди и ожидают дескрипторы в C#

Я имел следующий код в своем приложении в течение нескольких лет и никогда не видел проблему от него.

while ((PendingOrders.Count > 0) || (WaitHandle.WaitAny(CommandEventArr) != 1))
{
    lock (PendingOrders)
    {
       if (PendingOrders.Count > 0)
       {
           fbo = PendingOrders.Dequeue();
       }
       else
       {
           fbo = null;
       }
    }

    // Do Some Work if fbo is != null
}

Где CommandEventArr составлен из NewOrderEvent (автоматическое событие сброса) и ExitEvent (ручное событие сброса).

Но я не уверен, ориентировано ли это на многопотоковое исполнение (принимающий N потоки производителя, которые весь блокируют очередь прежде enqueing и один потребительский поток, который выполняет код выше). Кроме того, мы можем предположить что Очередь. Свойство количества просто возвращает один экземпляр значение Int32 из класса Очереди (без энергозависимого или взаимно блокируемого или блокировки, и т.д.).

Что обычный шаблон используется с Очередью и AutoResetEvent, чтобы зафиксировать это и сделать то, что я пытаюсь сделать с кодом выше?

(Отредактированный для изменения вопроса немного после того, как на это правильно указали та Очередь. Количество могло сделать что-либо и его конкретную реализацию).

5
задан Michael Covelli 30 April 2010 в 16:32
поделиться

4 ответа

Мне кажется довольно поточно-ориентированным, WaitAny () просто завершится немедленно, потому что событие уже установлено. Это , а не проблема.

Не нарушайте работающую синхронизацию потоков. Но если вам нужна лучшая мышеловка, вы можете рассмотреть BlockingQueue Джо Даффи в этой статье журнала . Более общая его версия доступна в .NET 4.0, System.Collections.Concurrent.BlockingCollection с ConcurrentQueue в качестве его практической реализации.

4
ответ дан 14 December 2019 в 01:03
поделиться

Использование ручных событий ...

ManualResetEvent[] CommandEventArr = new ManualResetEvent[] { NewOrderEvent, ExitEvent };

while ((WaitHandle.WaitAny(CommandEventArr) != 1))
{
    lock (PendingOrders)
    {
        if (PendingOrders.Count > 0)
        {
            fbo = PendingOrders.Dequeue();
        }
        else
        {
            fbo = null;
            NewOrderEvent.Reset();
        }
    }
}

Затем вам необходимо обеспечить блокировку и на стороне постановки в очередь:

    lock (PendingOrders)
    {
        PendingOrders.Enqueue(obj);
        NewOrderEvent.Set();
    }
2
ответ дан 14 December 2019 в 01:03
поделиться

Вы правы. Код не является потокобезопасным. Но не по той причине, о которой вы думаете.

AutoResetEvent в порядке. Хотя только потому, что вы установили блокировку и повторно протестировали PendingOrders.Count. Настоящая проблема в том, что вы вызываете PendingOrders.Count вне блокировки. Поскольку класс Queue не является потокобезопасным, ваш код не является потокобезопасным ... точка.

На самом деле у вас, вероятно, никогда не будет проблем с этим по двум причинам. Во-первых, свойство Queue.Count почти наверняка спроектировано так, чтобы никогда не оставлять объект в недоработанном состоянии. В конце концов, он, вероятно, просто вернет переменную экземпляра. Во-вторых, отсутствие барьера памяти для этого чтения не окажет существенного влияния на более широкий контекст вашего кода. Худшее, что может случиться, это то, что вы получите устаревшее чтение на одной итерации цикла, а затем полученная блокировка неявно создаст барьер памяти, и на следующей итерации произойдет новое чтение. Я предполагаю, что здесь есть только один элемент очереди потока. Ситуация значительно меняется, если их 2 или больше.

Однако позвольте мне прояснить это. У вас нет гарантии, что PendingOrders.Count не изменит состояние объекта во время его выполнения . И поскольку он не заключен в блокировку, другой поток может инициировать операцию с ним, пока он все еще находится в этом полуобеспеченном состоянии.

3
ответ дан 14 December 2019 в 01:03
поделиться

Вы должны использовать WaitAny только для этого, и убедиться, что он получает сигнал при каждом новом заказе, добавленном в коллекцию PendingOrders:

while (WaitHandle.WaitAny(CommandEventArr) != 1))
{
   lock (PendingOrders)
   {
      if (PendingOrders.Count > 0)
      {
          fbo = PendingOrders.Dequeue();
      }
      else
      {
          fbo = null;

          //Only if you want to exit when there are no more PendingOrders
          return;
      }
   }

   // Do Some Work if fbo is != null
}
0
ответ дан 14 December 2019 в 01:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: