C # Потоки и очереди - PullRequest
       16

C # Потоки и очереди

11 голосов
/ 27 апреля 2009

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

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, очередь пуста. Это должно быть невозможно, поскольку подсчет гарантирует, что там что-то есть, и я уверен, что ничто иное не «исключено».

Вопрос (ы):

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

Редактировать (уточнение):

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

Ответы [ 5 ]

11 голосов
/ 27 апреля 2009

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

Как указывает Бен, похоже, что у вас есть несколько человек, которые звонят в очередь.

Вы говорите, что уверены, что ничто другое не вызывает 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;
    }
}
3 голосов
/ 27 апреля 2009

Вот возможный ответ от на странице MSDN на эту тему:

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

Полагаю, вы правы - в какой-то момент возникает состояние гонки, и вы в конечном итоге вытаскиваете что-то, чего там нет.

Mutex или Monitor.Lock, вероятно, здесь уместен.

Удачи!

3 голосов
/ 27 апреля 2009

Вот, как мне кажется, проблемная последовательность:

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

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

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

2 голосов
/ 27 апреля 2009

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

С MSDN :

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

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

1 голос
/ 28 апреля 2009

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

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

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

C # не поставляется с реализацией очереди блокировки, но есть много других. Смотрите этот пример и этот один .

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