NOTE:
i did a complete rework of my question. you can see the original question via the change-history.
i\'m in the need of a "mighty&qu
You should begin with a generic Producer-Consumer queue and use that. Implementing this inside a Queue is not such a good idea, as this prevents you from using semaphores to signal threads (or, you could have public semaphores in your Queue, but that's a really bad idea).
As soon as the thread A has enqueued a single work item, it must signal a semaphore to notify thread B. When thread B has finished processing all items, it should signal a semaphore to notify everyone else that it has finished. Your main thread should be waiting for this second semaphore to know that everything is done.
[Edit]
First, you have a producer and a consumer:
public interface IProducer : IStoppable
{
///
/// Notifies clients when a new item is produced.
///
event EventHandler> ItemProduced;
}
public interface IConsumer : IStoppable
{
///
/// Performs processing of the specified item.
///
/// The item.
void ConsumeItem(T item);
}
public interface IStoppable
{
void Stop();
}
So, in your case, class creating the mail will need to fire the ItemProduced
event, and the class sending it will need to implement ConsumeItem
.
And then you pass these two instances to an instance of Worker
:
public class Worker
{
private readonly Object _lock = new Object();
private readonly Queue _queuedItems = new Queue();
private readonly AutoResetEvent _itemReadyEvt = new AutoResetEvent(false);
private readonly IProducer _producer;
private readonly IConsumer _consumer;
private volatile bool _ending = false;
private Thread _workerThread;
public Worker(IProducer producer, IConsumer consumer)
{
_producer = producer;
_consumer = consumer;
}
public void Start(ThreadPriority priority)
{
_producer.ItemProduced += Producer_ItemProduced;
_ending = false;
// start a new thread
_workerThread = new Thread(new ThreadStart(WorkerLoop));
_workerThread.IsBackground = true;
_workerThread.Priority = priority;
_workerThread.Start();
}
public void Stop()
{
_producer.ItemProduced -= Producer_ItemProduced;
_ending = true;
// signal the consumer, in case it is idle
_itemReadyEvt.Set();
_workerThread.Join();
}
private void Producer_ItemProduced
(object sender, ProducedItemEventArgs e)
{
lock (_lock) { _queuedItems.Enqueue(e.Item); }
// notify consumer thread
_itemReadyEvt.Set();
}
private void WorkerLoop()
{
while (!_ending)
{
_itemReadyEvt.WaitOne(-1, false);
T singleItem = default(T);
lock (_lock)
{
if (_queuedItems.Count > 0)
{
singleItem = _queuedItems.Dequeue();
}
}
while (singleItem != null)
{
try
{
_consumer.ConsumeItem(singleItem);
}
catch (Exception ex)
{
// handle exception, fire an event
// or something. Otherwise this
// worker thread will die and you
// will have no idea what happened
}
lock (_lock)
{
if (_queuedItems.Count > 0)
{
singleItem = _queuedItems.Dequeue();
}
}
}
}
} // WorkerLoop
} // Worker
That's the general idea, there may be some additional tweaks needed.
To use it, you need to have your classes implement these two interfaces:
IProducer mailCreator = new MailCreator();
IConsumer mailSender = new MailSender();
Worker worker = new Worker(mailCreator, mailSender);
worker.Start();
// produce an item - worker will add it to the
// queue and signal the background thread
mailCreator.CreateSomeMail();
// following line will block this (calling) thread
// until all items are consumed
worker.Stop();
The great thing about this is that: