Wednesday, 15 May 2013

WaitHandles - Auto/ManualResetEvent and Mutex



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 AutoResetEventManualResetEvent 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 (MutexAutoResetEvent 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/PulseMutex 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