Доброго времени суток!
не понятен вывод следующего кода (слегка измененный пример 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.. два раза?
Здравствуйте, Аноним, Вы писали:
А>вывод
А>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();
}