WaitHandles - Auto/ManualResetEvent and Mutex
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 Semaphoremechanism 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.
Auto/ManualResetEvent
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 usesWaitHandle.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 usedAutoResetEvent 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.
Example...
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();
}
}
|
Mutex
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 mutexvariable 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.
0 comments:
Post a Comment