вопрос по AutoResetEvent
От: Аноним  
Дата: 19.09.09 20:23
Оценка:
Доброго времени суток!

не понятен вывод следующего кода (слегка измененный пример Producer/Consumer из здесь
Автор(ы): Joseph Albahari
Дата: 24.03.2007
Подробно рассматривается работа с потоками — запуск, завершение, прерывание, блокировки, синхронизация, контексты синхронизации, особенности взаимодействия с апартаментами, а также потоковые возможности .NET — потоковые таймеры, пулы потоков, BackgroundWorker, асинхронные методы и делегаты.
В статье использован материал из книги Joseph Albahari, Ben Albahari "C# 3.0 in a Nutshell" — http://www.oreilly.com/catalog/9780596527570/
)


using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            ProducerConsumerQueue q = new ProducerConsumerQueue();
            Thread.Sleep(1000);    
            q.EnqueueTask("Hello");
            for (int i = 0; i < 5; i++) q.EnqueueTask("Say " + i);
            Thread.Sleep(100); 
            q.EnqueueTask("Goodbye!");

            Thread.Sleep(5000);
            q.EnqueueTask("Goodbye_II!");

            q.Dispose();
            Console.ReadLine();
        }
    }

    class ProducerConsumerQueue : IDisposable
    {
        
        EventWaitHandle wh = new AutoResetEvent(false);
        Thread worker;
        object locker = new object();
        Queue<string> tasks = new Queue<string>();

        public ProducerConsumerQueue()
        {
            worker = new Thread(Work);
            worker.Start();
        }

        public void EnqueueTask(string task)
        {
            lock (locker) tasks.Enqueue(task);
            wh.Set();
        }

        public void Dispose()
        {
            EnqueueTask(null);     // Signal the consumer to exit.
            worker.Join();          // Wait for the consumer's thread to finish.
            wh.Close();             // Release any OS resources.
        }

        void Work()
        {
            while (true)
            {
                string task = null;
                lock (locker)
                    if (tasks.Count > 0)
                    {
                        task = tasks.Dequeue();
                        if (task == null) 
                            return;
                    }

                if (task != null)
                {
                    Console.WriteLine("Performing task: " + task);
                    Thread.Sleep(500);  // simulate work...
                }
                else
                {
                    Console.WriteLine("Waiting.." );
                    wh.WaitOne();         // No more tasks - wait for a signal
                }
            }
        }
    }
}


вывод

Waiting..
Performing task: Hello
Performing task: Say 0
Performing task: Say 1
Performing task: Say 2
Performing task: Say 3
Performing task: Say 4
Performing task: Goodbye!
Waiting..
Waiting..
Performing task: Goodbye_II!

вопрос: почему Waiting.. два раза?
Re: вопрос по AutoResetEvent
От: Мизантроп  
Дата: 20.09.09 04:39
Оценка:
Здравствуйте, Аноним, Вы писали:

А>вывод


А>Waiting..

А>Performing task: Hello
А>Performing task: Say 0
А>Performing task: Say 1
А>Performing task: Say 2
А>Performing task: Say 3
А>Performing task: Say 4
А>Performing task: Goodbye!
А>Waiting..
А>Waiting..
А>Performing task: Goodbye_II!

А>вопрос: почему Waiting.. два раза?


Вы добавили задания 1, 2, или 1, 2, 3, неважно. Взвели эвент. Поток проснулся, сбросив эвент и начав обрабатывать задания. Пока он обрабатывает первое, Вы добавляете в очередь остальные задания, опять взведя эвент. Но поток-обработчик не обращается к событию, пока в очереди остаются задания. Таким обраазом в очереди все задания, эвент взведён. Обработчик выбрал все задания и перешёл к WaitOne, видит, что он взведён, выходит из ожидания, но очередь уже пуста, он обработал все задания на предыдущем шаге, и потому он опять уходит в ожидиние. Ну а потом Вы добавляете последнее задание и NULL, после чего всё завершается.

Избежать этого можно разными путями, один из возможных:

Заменить AutoResetEvent на ManualResetEvent, сбрасывать его потоком-обработчиком внутри секции Lock при пустой очереди. Взводить, соответственно, тоже внутри Lock, как-то так:

public void EnqueueTask(string task)
{
    lock (locker)
    {
        tasks.Enqueue(task);
        if (tasks.Count == 1) wh.Set();
    }
}


Выборка:
lock (locker)
{
    if (tasks.Count > 0)
    {
        task = tasks.Dequeue();
        if (task == null)
            return;
    }
    if (tasks.Count == 0) wh.Reset();
}
"Нормальные герои всегда идут в обход!"
Re[2]: вопрос по AutoResetEvent
От: Аноним  
Дата: 20.09.09 09:59
Оценка:
Мизантроп,
спасибо!
Re[3]: вопрос по AutoResetEvent
От: Мизантроп  
Дата: 20.09.09 12:25
Оценка:
Здравствуйте, Аноним, Вы писали:

Заранее прошу извинить, если мой совет покажется непрошенным или неуместным.
Как я уже сказал, это только один из возможных вариантов. На вскидку можно назвать ещё 4-5, разной степени различности и с разной областью применимости, попробуйте их найти. Так как для Вас это, похоже, тренировочная задача, то это будет и интересно, и полезно, а в будущем позволит делать осознанный выбор.
"Нормальные герои всегда идут в обход!"
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.