您的位置:首页 > 其它

Thread Synchronization 一 ManualResetEvent and AutoResetEvent

2011-11-30 17:33 507 查看
http://www.yoda.arachsys.com/csharp/threads/waithandles.shtml

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
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.

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 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();
}
}

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
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.

-------------------

The following is my test code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleApplication2
{
class ManualResetEventTest
{

// mre is used to block and release threads manually. It is
// created in the unsignaled state.
private static ManualResetEvent mre = new ManualResetEvent(false);

static void Main()
{
Console.WriteLine("\nStart 3 named threads that block on a ManualResetEvent:\n");

for(int i = 0; i <= 2; i++)
{
Thread t = new Thread(ThreadProc);
t.Name = "Thread_" + i;
t.Start();
}

Thread.Sleep(500);
Console.WriteLine("\nWhen all three threads have started, press Enter to call Set()" +
"\nto release all the threads.\n");
Console.ReadLine();

mre.Set();

Thread.Sleep(500);
Console.WriteLine("\nWhen a ManualResetEvent is signaled, threads that call WaitOne()" +
"\ndo not block. Press Enter to show this.\n");
Console.ReadLine();

for(int i = 3; i <= 4; i++)
{
Thread t = new Thread(ThreadProc);
t.Name = "Thread_" + i;
t.Start();
}

Thread.Sleep(500);
Console.WriteLine("\nPress Enter to call Reset(), so that threads once again block" +
"\nwhen they call WaitOne().\n");
Console.ReadLine();

mre.Reset();

// Start a thread that waits on the ManualResetEvent.
Thread t5 = new Thread(ThreadProc);
t5.Name = "Thread_5";
t5.Start();

Thread.Sleep(500);
Console.WriteLine("\nPress Enter to call Set() and conclude the demo.");
Console.ReadLine();

mre.Set();

mre.Reset();

Console.WriteLine("111111");

mre.WaitOne();

Console.WriteLine("2222222");

// If you run this example in Visual Studio, uncomment the following line:
//Console.ReadLine();
}

private static void ThreadProc()
{
string name = Thread.CurrentThread.Name;

Console.WriteLine(name + " starts and calls mre.WaitOne()");

mre.WaitOne();

Console.WriteLine(name + " ends.");
}
}
}

/* This example produces output similar to the following:

Start 3 named threads that block on a ManualResetEvent:

Thread_0 starts and calls mre.WaitOne()
Thread_1 starts and calls mre.WaitOne()
Thread_2 starts and calls mre.WaitOne()

When all three threads have started, press Enter to call Set()
to release all the threads.

Thread_2 ends.
Thread_0 ends.
Thread_1 ends.

When a ManualResetEvent is signaled, threads that call WaitOne()
do not block. Press Enter to show this.

Thread_3 starts and calls mre.WaitOne()
Thread_3 ends.
Thread_4 starts and calls mre.WaitOne()
Thread_4 ends.

Press Enter to call Reset(), so that threads once again block
when they call WaitOne().

Thread_5 starts and calls mre.WaitOne()

Press Enter to call Set() and conclude the demo.

Thread_5 ends.
*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: