I have a producer-consumer scenario in ASP.NET. I designed a Producer
class, a Consumer
class and a class for holding the shared objects and responsible for communication between Producer and Consumer, lets call it Mediator
. Because I fork the execution path at start-up (in parent object) and one thread would call Producer.Start()
and another thread calls Consumer.Start()
, I need to pass a reference of Mediator
to both Producer
and Consumer
(via Constructor
). Mediator
is a smart class which will optimize many things like length of it's inner queue but for now consider it as a circular blocking queue. Producer
would enqueues new objects to Mediator
until the queue gets full and then Producer
would block. Consumer
dequeues objects from Mediator
until there's nothing in the queue. For signaling between threads, I implemented two methods in Mediator
class: Wait()
and Pulse()
. The code is something like this:
Class Mediator
{
private object _locker = new object();
public void Wait()
{
lock(_locker)
Monitor.Wait(_locker);
}
public void Pulse()
{
lock(_locker)
Monitor.Pulse(_locker);
}
}
// This way threads are signaling:
Class Consumer
{
object x;
if (Mediator.TryDequeue(out x))
// Do something
else
Mediator.Wait();
}
Inside Mediator I use this.Pulse()
every time something is Enqueued or Dequeued so waiting threads would be signaled and continue their work.
But I encounter deadlocks and because I have never used this kind of design for signaling threads, I'm not sure if something is wrong with the design or I'm doing something wrong elsewhere ?
Thanks
There is not much code here to go on, but my best guess is that you have a live-lock problem. If Mediator.Pulse
is called before Mediator.Wait
then the signal gets lost even though there is something in the queue. Here is the standard pattern for implementing the blocking queue.
public class BlockingQueue<T>
{
private Queue<T> m_Queue = new Queue<T>();
public void Enqueue(T item)
{
lock (m_Queue)
{
m_Queue.Enqueue(item);
Monitor.Pulse(m_Queue);
}
}
public T Dequeue()
{
lock (m_Queue)
{
while (m_Queue.Count == 0)
{
Monitor.Wait(m_Queue);
}
return m_Queue.Dequeue();
}
}
}
Notice how Monitor.Wait
is only called when the queue is empty. Also notice how it is being called in a while
loop. This is because a Wait
does not have priority over a Enter
so a new thread coming into Dequeue
could take the last item even though a call to Wait
is ready to return. Without the loop a thread could attempt to remove an item from an empty queue.
If you can use .NET 4 your best bet would be to use BlockingCollection<T>
(http://msdn.microsoft.com/en-us/library/dd267312.aspx) which handles queueing, dequeuing, and limits on queue length.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With