2009-04-27 3 views
11

Речь идет не о различных методах, которые я мог использовать или использовать, чтобы использовать очереди наилучшим образом, скорее то, что я видел, что не имеет смысла для меня.C# Threading and Queues

void Runner() { 
    // member variable 
    queue = Queue.Synchronized(new Queue()); 
    while (true) { 
     if (0 < queue.Count) { 
      queue.Dequeue(); 
     } 
    } 
} 

Это выполняется в одном потоке:

var t = new Thread(Runner); 
t.IsBackground = true; 
t.Start(); 

Другие события "Ставить" ИНГ еще где. То, что я видел, происходит в течение определенного периода времени, Dequeue на самом деле бросает InvalidOperationException, очередь пуста. Это должно быть невозможно увидеть, как подсчет гарантирует, что там что-то есть, и я уверен, что больше ничего не происходит «Dequeue» ing.

вопрос (ы):

  1. Возможно ли, что Enqueue фактически увеличивает количество до того, как элемент полностью на очереди (независимо от того, что значит ...)?
  2. Возможно ли, что нить каким-то образом перезапустится (истекает, переиздается ...) в операторе Dequeue, но сразу же после того, как он уже удалил элемент?

Edit (осветление):

Эти кодовые части являются частью класса Wrapper, который реализует фон вспомогательный поток. Dequeue здесь является единственным Dequeue, и все Enqueue/Dequeue находятся в переменной Synchronized (queue).

+0

Из-за ответа Райана ... это настоящий код или просто упрощенный пример? Если это реальный код, вы должны действительно думать об изменении цикла - опрос очереди вместо синхронизации чтения с писателями - плохой дизайн. Вы тратите миллионы процессорных циклов, чтобы нагреть комнату. –

+0

Это пример, чтобы добраться до мяса проблемы. Там есть Thread.Sleep, процессор не забивается. Причина, по которой я выбрал процесс опроса вместо чтения/чтения с синхронизацией, объясняется тем, что в очереди есть что-то в ней почти все время. В нашем багажнике, хотя я добавил AutoResetEvent, чтобы поиграть. Как я уже сказал наверху, я не очень беспокоюсь о реализации здесь. Кажется, что существует настоящая проблема с этой моделью потоков, будь то правильно или неправильно. – neouser99

+0

От взгляда на ваш код у вас есть, по крайней мере, основной поток и поток, в котором вы думаете, что вызывается Dequeue. Почему бы не назвать ваши потоки, и каждый раз, когда вызывается Dequeue, регистрируйте имя потока с трассировкой стека. Вы могли бы обнаружить, что что-то в основном потоке ведет себя так, как вы этого не ожидали. –

ответ

11

Используя Reflector, вы можете видеть, что нет, счет не увеличивается до тех пор, пока элемент не будет добавлен.

Как указывает Бен, кажется, что у вас есть несколько людей, вызывающих dequeue.

Вы говорите, что вы уверены, что ничто другое не вызывает dequeue. Это потому, что у вас есть только один поток, вызывающий dequeue? Является ли dequeue названным где-либо еще вообще?

EDIT:

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

Как долго он работал, прежде чем вы получили ошибки? Возможно, вы можете поделиться немного больше кода.

class Program 
{ 
    static Queue q = Queue.Synchronized(new Queue()); 
    static bool running = true; 

    static void Main() 
    { 
     Thread producer1 = new Thread(() => 
      { 
       while (running) 
       { 
        q.Enqueue(Guid.NewGuid()); 
        Thread.Sleep(100); 
       } 
      }); 

     Thread producer2 = new Thread(() => 
     { 
      while (running) 
      { 
       q.Enqueue(Guid.NewGuid()); 
       Thread.Sleep(25); 
      } 
     }); 

     Thread consumer = new Thread(() => 
      { 
       while (running) 
       { 
        if (q.Count > 0) 
        { 
         Guid g = (Guid)q.Dequeue(); 
         Console.Write(g.ToString() + " "); 
        } 
        else 
        { 
         Console.Write(" . "); 
        } 
        Thread.Sleep(1); 
       } 
      }); 
     consumer.IsBackground = true; 

     consumer.Start(); 
     producer1.Start(); 
     producer2.Start(); 

     Console.ReadLine(); 

     running = false; 
    } 
} 
+0

Очень хорошо, у меня на самом деле есть тестовый пример, который выглядит довольно близко к этому, и он получает суть. Служба работает около 2+ недель, я думаю, прежде чем она разбилась. – neouser99

+8

На этом этапе я могу предложить следующее: (1) Вы вошли в раздел If, (2) Solar Flare перевернул ваш бит на ноль, (3) Ваша очередь выбрала исключение. Это действительно должно было быть исключено из SolarFlareException, но что бы это ни было. Картофех, Потахто. –

3

Вот что я думаю, что проблематичная последовательность:

  1. (0 < queue.Count) истинно, очередь не пуста.
  2. Этот поток получает preempted, а другой поток работает.
  3. Другая нить удаляет элемент из очереди, опуская его.
  4. Этот поток возобновляет выполнение, но теперь находится в блоке if и пытается удалить пустой список.

Однако, вы говорите, ничего другого не ... извлечение из

Попробуйте выводит количество внутри, если блок. Если вы видите числа переходов числа вниз, кто-то другой бросается в очередь.

+2

Это была моя первая мысль, но он сказал: «Я уверен, что больше ничего не происходит» Dequeue »ing.» – BFree

+0

Смысл моих мыслей, но я думаю, что он должен прояснить факты, которые делают его настолько уверенным, что ничто другое не призывает Dequeue. –

+0

+1. Это состояние гонки между Count и Dequeue, независимо от того, является ли какой-либо другой поток * в настоящее время * dequeuing. Лучше всего исправить состояние гонки и перейти к реальной проблеме. –

3

Вот возможный ответ от the MSDN page на эту тему:

Перечислив через коллекцию внутренне не поточно- процедуры. Даже если коллекция синхронизирована, другие потоки могут по-прежнему изменить коллекцию, из-за чего перечислитель выдает исключение. Чтобы гарантировать безопасность резьбы во время регистрации , вы можете либо заблокировать коллекцию в течение всего перечисления , либо за исключением исключений в результате изменений, внесенных другими потоками .

Мое предположение, что вы правы - в какой-то момент происходит состояние гонки, и вы заканчиваете тем, что не существует.

Возможно, Mutex или Monitor.Lock подходит именно здесь.

Удачи вам!

2

Являются ли другие области, которые являются «входящими» данными, также с использованием одного и того же объекта с синхронизированной очередью? Чтобы Queue.Synchronized был потокобезопасным, все операции Enqueue и Dequeue должны использовать один и тот же объект с синхронизированной очередью.

От MSDN:

Чтобы гарантировать безопасность потока в очереди, все операции должны быть сделаны только через эту оболочку.

Отредактировано: Если вы цикл по многим пунктам, которые включают тяжелые вычисления, или если вы используете долгосрочный цикл потоков (связь и т.д.), вы должны рассмотреть с функцией ожидания, такие как System.Threading.Thread.Sleep, System.Threading.WaitHandle.WaitOne , System.Threading.WaitHandle.WaitAll, или System.Threading.WaitHandle.WaitAny, в противном случае это может привести к снижению производительности системы.

+0

"[...] убедитесь, что у вас есть System.Threading.Thread.Sleep (1) в любом поточном цикле [...]"!?! Какие? Почему вы должны делать что-то подобное? –

+0

Ahhh ... понял. Вы имеете в виду while (true) {}. И нет - вы действительно не должны добавлять Thread.Sleep() в этот цикл. Не следует добавлять Thread.Sleep() в любой цикл. Это очень плохой дизайн, и он должен быть исправлен - поток читателя должен синхронизироваться с потоком записи вместо опроса очереди. –

+1

Сон позволяет системе делиться процессором и ресурсами, иначе поток будет потреблять значительные ресурсы (в некоторых случаях голодает другие потоки и процессы). Очевидно, что это пример кода, но есть время (правда) без условия выхода, поэтому я упомянул об этом. Sleep (0) имеет проблемы с отказом управления для более низких приоритетов, поэтому рекомендуется Sleep (1). Если цикл потока синхронизирован или имеет ограниченный промежуток времени, тогда может не потребоваться «Сон». Я видел ряд сервисов Windows, где один оператор Sleep меняет использование ЦП с 80-100% до <1%. – Ryan

1

вопрос 1: Если вы используете синхронизированную очередь, то: нет, вы в безопасности! Но вам нужно использовать синхронизированный экземпляр с обеих сторон, поставщика и фидера.

вопрос 2: завершение рабочего потока, когда нет работы, является простой задачей. Тем не менее, вам в любом случае нужен поток мониторинга или очередь запускает поток рабочего фона, когда очередь имеет что-то делать. Последнее похоже больше на шаблон ActiveObject, чем простая очередь (в которой Single-Responsibily-Pattern говорит, что он должен выполнять только очередность).

Кроме того, я бы выбрал блокирующую очередь вместо вашего кода выше. То, как работает ваш код, требует мощности процессора, даже если нет работы. Блокирующая очередь позволяет вашему рабочему потоку спать всякий раз, когда нечего делать. У вас может быть несколько спящих потоков без использования мощности процессора.

C# не имеет реализации блокировки очереди, но там много. См. Это example и это one.