The proper way to view Monitor.Wait
and Monitor.Pulse
/PulseAll
is not as providing a means of waiting, but rather (for Wait
) as a means of letting the system know that the code is in a waiting loop which can't exit until something of interest changes, and (for Pulse
/PulseAll
) as a means of letting the system know that code has just changed something that might cause satisfy the exit condition some other thread's waiting loop. One should be able to replace all occurrences of Wait
with Sleep(0)
and still have code work correctly (even if much less efficiently, as a result of spending CPU time repeatedly testing conditions that haven't changed).
For this mechanism to work, it is necessary to avoid the possibility of the following sequence:
The code in the wait loop tests the condition when it isn't satisfied.
The code in another thread changes the condition so that it is satisfied.
The code in that other thread pulses the lock (which nobody is yet waiting on).
The code in the wait loop performs a Wait
since its condition wasn't satisfied.
The Wait
method requires that the waiting thread have a lock, since that's the only way it can be sure that the condition it's waiting upon won't change between the time it's tested and the time the code performs the Wait
. The Pulse
method requires a lock because that's the only way it can be sure that if another thread has "committed" itself to performing a Wait
, the Pulse
won't occur until after the other thread actually does so. Note that using Wait
within a lock doesn't guarantee that it's being used correctly, but there's no way that using Wait
outside a lock could possibly be correct.
The Wait
/Pulse
design actually works reasonably well if both sides cooperate. The biggest weaknesses of the design, IMHO, are (1) there's no mechanism for a thread to wait until any of a number of objects is pulsed; (2) even if one is "shutting down" an object such that all future wait loops should exit immediately (probably by checking an exit flag), the only way to ensure that any Wait
to which a thread has committed itself will get a Pulse
is to acquire the lock, possibly waiting indefinitely for it to become available.