С# Threading и 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();

Другими событиями являются "Enqueue" else. То, что я видел, происходит в течение определенного периода времени, Dequeue на самом деле бросает InvalidOperationException, очередь пуста. Это должно быть невозможно увидеть, как подсчет гарантирует, что там что-то есть, и я уверен, что ничто иное не "Dequeue" ing.

Вопрос (ы):

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

Изменить (пояснение):

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

Ответ 1

Используя 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;
    }
}

Ответ 2

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

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

Тем не менее, вы говорите, что ничего другого не обманывает...

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

Ответ 3

Здесь можно ответить на страницу MSDN по этой теме:

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

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

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

Удачи!

Ответ 4

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

От MSDN:

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

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

Ответ 5

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

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

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

С# не имеет реализацию блокировки очереди, но там много. См. Этот пример и один.