Unfortunately, none of Wait (), Pulse (), or PulseAll () has the magic property that you want - that with this API you automatically avoid a deadlock.
Consider the following code
object incomingMessages = new object(); //signal object LoopOnMessages() { lock(incomingMessages) { Monitor.Wait(incomingMessages); } if (canGrabMessage()) handleMessage(); // loop } ReceiveMessagesAndSignalWaiters() { awaitMessages(); copyMessagesToReadyArea(); lock(incomingMessages) { Monitor.PulseAll(incomingMessages); //or Monitor.Pulse } awaitReadyAreaHasFreeSpace(); }
This code will close! Maybe not today, maybe not tomorrow. Most likely, when your code is under stress, because suddenly it has become popular or important, and you are called in to fix an urgent problem.
Why?
In the end, the following will happen:
- All consumer flows do some work.
- Messages arrive, the ready area cannot contain more messages, and PulseAll () is called.
- No user wakes up because no one is waiting
- All consumer flows are called Wait () [DEADLOCK]
This particular example assumes that the manufacturer thread will never call PulseAll () again, because it no longer has space to enter messages. But there are many, many broken versions of this code. People will try to make it more reliable by changing the line, for example, by making Monitor.Wait(); at
if (!canGrabMessage()) Monitor.Wait(incomingMessages);
Unfortunately, this is still not enough to fix it. To fix this, you also need to change the blocking area in which Monitor.PulseAll() is called:
LoopOnMessages() { lock(incomingMessages) { if (!canGrabMessage()) Monitor.Wait(incomingMessages); } if (canGrabMessage()) handleMessage(); // loop } ReceiveMessagesAndSignalWaiters() { awaitMessagesArrive(); lock(incomingMessages) { copyMessagesToReadyArea(); Monitor.PulseAll(incomingMessages); //or Monitor.Pulse } awaitReadyAreaHasFreeSpace(); }
The key point is that in a fixed code, locks limit the possible sequence of events:
Consumer flows do their work and cycles
This thread gets blocked
And thanks to the lock, it’s now true that either:
but. Messages have not yet arrived at the ready area, and it releases the lock by calling Wait () BEFORE the message receiver thread can receive the lock and copy more messages to the ready area or
b. Messages already arrive in the ready area and receive INSTEAD OF messages that call Wait (). (And although he makes this decision, it is impossible for the message recipient thread, for example, to acquire a lock and copy more messages to the ready area.)
As a result, the source code problem now never arises: 3. When PulseEvent () is called. You can’t wake a consumer because no one is waiting
Now pay attention to the fact that in this code you need to precisely fix the blocking area. (If I'm really right!)
And also, since you must use lock (or Monitor.Enter() , etc.) to use Monitor.PulseAll() or Monitor.Wait() without locking, you still have to worry about the possibility of other deadlocks that come from -for this lock.
Bottom line: these APIs are also easy to screw and slow down, i.e. quite dangerous