Threading issue with Monitor.Wait () and Monitor.Pulse ()

I have a consumer producer script in ASP.NET. I developed the Producer class, the Consumer class, and the class for storing shared objects and those responsible for communication between the Producer and Consumer by calling him the Mediator . Since I run the execution path at startup (in the parent), and one thread calls Producer.Start() and the other thread calls Consumer.Start() , I need to pass the Mediator link to Producer and Consumer (via Constructor ). Mediator is a smart class that optimizes many things, such as the length of the internal queue, but for now considers it as a cyclical blocking queue. Producer places new objects in the Mediator until the queue is full, and then Producer blocks. Consumer removes objects from the Mediator until there is nothing in the queue. For signaling between threads, I implemented two methods in the Mediator class: Wait() and Pulse() . The code looks 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 the broker, I use this.Pulse() every time something is called or called, so the waiting threads will be signaled and continue to work.

But I was faced with deadlocks and because I never used such a design for signal flows, am I not sure if something is wrong with the design or am I doing something wrong elsewhere?

thanks

+4
source share
6 answers

Nothing works with the design.

The problem arises when using Monitor.Wait() and Monitor.Pulse() , when you do not know which thread will perform this work first (producer or consumer). In this case, using AutoResetEvent solves the problem. Think about the consumer when he reaches the section where he should consume the data produced by the manufacturer. Maybe he gets to the point where the producer starts the pulse, then everything will be fine, but what if the consumer gets there after the manufacturer signals. Yes, then you ran into a dead end because the producer already called Monitor.Pulse() for this section and did not repeat it. Using AutoResetEvent , you are sure that the consumer is waiting for a signal from the manufacturer, and if the manufacturer has already signaled before the consumer even reaches the section, the gates will be open and the consumer will continue.

In order to use Monitor.Wait() and Monitor.Pulse() inside the mediator to signal pending flows.

+2
source

There is not much code here, but I suppose you have a live-lock problem. If Mediator.Pulse is called before Mediator.Wait , then the signal is lost, even if there is something in the queue. Here is a standard template for implementing a 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(); } } } 

Note that Monitor.Wait is called only when the queue is empty. Also pay attention to how it is called in a while . This is because Wait does not take precedence over Enter , so a new thread entering Dequeue can accept the last element, even if the Wait call is ready to return. Without a loop, a thread may try to remove an item from an empty queue.

+8
source

If you can use .NET 4, the best option would be to use BlockingCollection<T> (http://msdn.microsoft.com/en-us/library/dd267312.aspx), which handles the queue, deletion, and queue length limits.

+4
source

Is it possible that a dead end occurs because Pulse does not save state? This means that if Producer calls Pulse before / after Consumer calls Wait , then the Wait block will block. This is a note in the documentation for Monitor.Pulse

In addition, you should know that object x = new object(); is extraneous - an outgoing call initializes x , so the created object will go out of scope with a TryDequeue call.

+1
source

It is hard to say if the entered code sample.

  • Is the castle held somewhere else? Inside the intermediary?
  • Are threads only parked when a lock is received, and not in a valid Wait call?
  • Have you paused threads in the debugger to find out what the current state is?
  • Have you tried a simple test by simply queuing a simple single value and making it work? Or is the intermediary quite complicated at the moment?

Until more details appear in the middleman class and your producer class, these are some wild assumptions. It seems that some thread might hold the lock when you are not expecting this. As soon as you are pulsating, you need to release the lock in any thread, possibly leaving the "lock" area. So, if somewhere in the mediator there is a lock, and then Pulse is called, you need to exit the outermost area where the lock is held, and not just in the pulse.

+1
source

Can you reorganize the usual line of consumers / producers? Then it could handle queues and dumping and stream signaling in the same class, so there is no need to skip public locks. The decanning process can be handled through the delegate. I can send an example if you wish.

+1
source

All Articles