Monitor.Wait/Pulse
isn't the only way of waiting for
something to happen in one thread and telling that thread that it's
happened in another. Win32 programmers have been using various other
mechanisms for a long time, and these are exposed by the
AutoResetEvent
, ManualResetEvent
and
Mutex
classes, all of which derive from
WaitHandle
. All of these classes are in the
System.Threading
namespace. (The Win32
Semaphore
mechanism does not have a managed wrapper in .NET
1.1. It's present in .NET 2.0, but if you need to use it before then, you
could either wrap it yourself using P/Invoke, or write your own counting
semaphore class.)
Some people may be surprised to learn that using these classes can be
significantly slower than using the various Monitor
methods.
I believe this is because going "out" of managed code into native Win32
calls and back "in" again is expensive compared with the entirely managed
view of things which Monitor
provides. A reader has also explained
that monitors are implemented in user mode, whereas using wait handles require
switching into kernel mode, which is fairly expensive.
WaitHandle
itself only exposes a few useful instance methods/properties:
WaitOne()
- used to wait for the handle to be free/signalled.
The exact meaning of this depends on the concrete type being used (Mutex
,
AutoResetEvent
or ManualResetEvent
).
Close()/Dispose()
- used to release the resources used by the handle.
Handle
- used to get the native handle being wrapped. Most developers
won't need to use this.
In addition, it has two useful static methods which deal with sets of WaitHandles
:
WaitAny()
- used to wait for any of the handles in a set to be free/signalled.
WaitAll()
- used to wait for all of the handles in a set to be free/signalled.
All of the WaitXXX()
methods have overloads allowing you
to specify a timeout and whether or not to exit the "synchronization
domain". The default value is false
. What is a synchronization domain, you ask?
Well, it's to do with some automatic thread handling that .NET has to offer in the guise of
Transactional COM+. Most .NET developers won't need to use this, but Juval Löwy has an
article
on the topic
if you wish to find out more, and likewise Richard Grimes wrote one
for Dr. Dobb's journal.
The two "event" classes (which are entirely different from .NET events
- don't get the two confused) come as a sort of pair, and are very
similar. You can think of them like doors - when they're in the
"signalled" (or "set") state they're open, and when they're in the
"non-signalled" (or "reset") state, they're closed. A call to
WaitOne()
waits for the door to be opened so the thread can
"go through it" in some sense. The difference between the two classes is
that an AutoResetEvent
will reset itself to the
non-signalled state immediately after a call to WaitOne()
-
it's as if anyone going through the door closes it behind them. With a
ManualResetEvent
, you have to tell the thread to reset it
(close the door) when you want to make calls to WaitOne()
block again. Both classes can manually be set or reset at any time, by any
thread, using the Set
and Reset
methods,
and can be created in the signalled/set or non-signalled/reset state.
(These methods return a boolean value saying whether or not they were
successful, but the documentation doesn't state why they might fail.)
Here's some sample code which simulates 10 runners. Each runner is
passed a ManualResetEvent
which is initially non-signalled.
When the runner completes the race, it signals the event. The main thread
uses WaitHandle.WaitAny
to wait for the first runner to finish,
and uses the value returned by the method to say who won the race. It
then uses WaitHandle.WaitAll
to wait for everyone to finish.
Note that if we'd used AutoResetEvent
instead, we'd have to call
Set
on the event of the winner, as it would have been reset
when we detected it being set with the call to WaitAny
.
using System; using System.Threading; class Test { static void Main() { ManualResetEvent[] events = new ManualResetEvent[10]; for (int i=0; i < events.Length; i++) { events[i] = new ManualResetEvent(false); Runner r = new Runner(events[i], i); new Thread(new ThreadStart(r.Run)).Start(); } int index = WaitHandle.WaitAny(events); Console.WriteLine ("***** The winner is {0} *****", index); WaitHandle.WaitAll(events); Console.WriteLine ("All finished!"); } } class Runner { static readonly object rngLock = new object(); static Random rng = new Random(); ManualResetEvent ev; int id; internal Runner (ManualResetEvent ev, int id) { this.ev = ev; this.id = id; } internal void Run() { for (int i=0; i < 10; i++) { int sleepTime; // Not sure about the thread safety of Random... lock (rngLock) { sleepTime = rng.Next(2000); } Thread.Sleep(sleepTime); Console.WriteLine ("Runner {0} at stage {1}", id, i); } ev.Set(); } } |
Whereas Auto/ManualResetEvent
have a lot in common with
using Monitor.Wait/Pulse
, Mutex
has even
more in common with Monitor.Enter/Exit
. A mutex has a
count of the number of times it's been acquired, and a thread which
is the current owner. If the count is zero, it has no owner and it
can be acquired by anyone. If the count is non-zero, the current
owner can acquire it however many times they like without blocking,
but any other thread has to wait until the count becomes zero before
they can acquire it. The WaitXXX()
methods are used to
acquire the mutex, and ReleaseMutex()
is used by the
owner thread to decrease the count by one. Only the owner can
decrease the count.
So far, so much like Monitor
. The difference is that
a Mutex
is a cross-process object - the same mutex
can be used in many processes, if you give it a name. A thread in
one process can wait for a thread in another process to release
the mutex, etc. When you construct a named mutex, you should be
careful about making assumptions as to whether or not you will
be able to acquire initial ownership of it. Fortunately, there is
a constructor which allows the code to detect whether the system
has created a whole new mutex or whether it's used an existing one.
If the constructor requested initial ownership, it will only have
been granted it if it created a new mutex - even if the existing
mutex can immediately be acquired.
Mutex names should start with either "Local\" or "Global\" to indicate whether they should be created in the local or global namespace respectively. (I believe that local is the default, but why take the risk? Make it explicit in the name.) If you create a mutex in the global namespace, it is shared with other users logged into the same machine. If you create a mutex in the local namespace, it is specific to the current user. Make sure you pick a suitably unique name so you don't clash with other programs.
To be honest, I think the principle use that mutexes will be put
to in .NET is the one mentioned earlier - detecting that another
instance of an application is already running. Most people don't
need inter-process communication on this kind of level. The other
use is to enable you to block until either one or all of a set of
WaitHandles
is released. For other purposes, where
Monitor
is good enough, I suggest using that - especially
as C# has the lock
statement specifically to support it.
Here's an example of detecting a running application, however:
using System; using System.Threading; class Test { static void Main() { bool firstInstance; using (Mutex mutex = new Mutex(true, @"Global\Jon.Skeet.MutexTestApp", out firstInstance)) { if (!firstInstance) { Console.WriteLine ("Other instance detected; aborting."); return; } Console.WriteLine ("We're the only instance running - yay!"); for (int i=0; i < 10; i++) { Console.WriteLine (i); Thread.Sleep(1000); } } } } |
Run the example in two different console windows - one will count to
ten slowly; the other will abort after it detects that the other
application instance is running. Note the using
statement
around the mutex: this should extend across the whole of the application's
execution, otherwise another instance would be able to create a new mutex
with the same name, after the old one had been destroyed. For instance,
suppose you use a local variable without a using
statement, like this:
using System; using System.Threading; class Test { static void Main() { bool firstInstance; // Bad code - do not use! Mutex mutex = new Mutex(true, @"Global\Jon.Skeet.MutexTestApp", out firstInstance); if (!firstInstance) { Console.WriteLine ("Other instance detected; aborting."); return; } Console.WriteLine ("We're the only instance running - yay!"); for (int i=0; i < 10; i++) { Console.WriteLine (i); Thread.Sleep(1000); } } } |
In that case, you'd probably find that everything would work fine under debug, where
the GC is very conservative about what it collects. When not running under the debugger,
however, the GC can tell that the mutex
variable isn't used after its initial
assignment, so for the main duration of the app, it can be garbage collected at any time
- and that destroys the mutex! The using
statement shown earlier is
only one way round this. You could make it a static variable instead, or use
GC.KeepAlive(mutex);
at the end of the method to make sure that the GC doesn't
ignore the variable.
Back to the main C# page.