views:

431

answers:

6

If you have two threads within an application, and you don't want them to run a certain piece of code simultaneously, you can just put a lock around the piece of code, like this:

lock (someObject) {
    // ... some code
}

But how do you do the same thing across separate processes? I thought this is what you use a "global mutex" for, so I tried the Mutex class in various ways, but it doesn't seem to fulfill my requirements, which are:

  • If you're the only instance, go ahead and run the code.
  • If you're the second instance, wait for the first one to finish, then run the code.
  • Don't throw exceptions.

Problems I ran into:

  • Just instantiating a Mutex object in a using(){...} clause doesn't seem to do anything; the two instances still happily run concurrently
  • Calling .WaitOne() on the Mutex causes the first instance to run and the second to wait, but the second waits indefinitely, even after the first calls .ReleaseMutex() and leaves the using(){} scope.
  • .WaitOne() throws an exception when the first process exits (System.Threading.AbandonedMutexException).

How do I solve this? Solutions that don't involve Mutex are very welcome, especially since Mutex appears to be Windows-specific.

+2  A: 

I have successfully used Mutex for exactly this purpose and can confirm that it works, though there were a few quirks.

I have a full working code example at home. Just post a comment to this answer if you would like me to add the code example this evening.

UPDATE:

Here's the stripped-down code from my production app. This is a console app but the same principal should apply to any type of application. Run with a command line argument of

--mutex 

to test the mutex logic.

Note that in my case the mutex really guards most of the process, but there's no reason you have to use it that way. That's just what was needed in this case.

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace MyNameSpace
{
    class Program
    {
        // APP_GUID can be any unique string.  I just opted for a Guid.  This isn't my real one :-)
        const string APP_GUID = "1F5D24FA-7032-4A94-DA9B-F2B6240F45AC";

        static int Main(string[] args)
        {
            bool testMutex = false;
            if (args.Length > 0 && args[0].ToUpper() == "--MUTEX")
            {
                testMutex = true;
            }

            // Got variables, now only allow one to run at a time.

            int pid = System.Diagnostics.Process.GetCurrentProcess().Id;

            int rc = 0;

            Mutex mutex = null;
            bool obtainedMutex = false;
            int attempts = 0;
            int MAX_ATTEMPTS = 4;

            try
            {
                mutex = new Mutex(false, "Global\\" + APP_GUID);

                Console.WriteLine("PID " + pid + " request mutex.");

                while (!obtainedMutex && attempts < MAX_ATTEMPTS)
                {
                    try
                    {
                        if (!mutex.WaitOne(2000, false))
                        {
                            Console.WriteLine("PID " + pid + " could not obtain mutex.");
                            // Wait up to 2 seconds to get the mutex
                        }
                        else
                        {
                            obtainedMutex = true;
                        }
                    }
                    catch (AbandonedMutexException)
                    {
                        Console.WriteLine("PID " + pid + " mutex abandoned!");
                        mutex = new Mutex(false, "Global\\" + APP_GUID); // Try to re-create as owner
                    }

                    attempts++;
                }

                if (!obtainedMutex)
                {
                    Console.WriteLine("PID " + pid + " gave up on mutex.");
                    return 102;
                }


                Console.WriteLine("PID " + pid + " got mutex.");

                // This is just to test the mutex... keep one instance open until a key is pressed while
                // other instances attempt to acquire the mutex
                if (testMutex)
                {
                    Console.Write("ENTER to exit mutex test....");
                    Console.ReadKey();
                    return 103;
                }

                // Do useful work here

            }
            finally
            {
                if (mutex != null && obtainedMutex) mutex.ReleaseMutex();
                mutex.Close();
                mutex = null;
            }

            return rc;
        }
    }
}
Eric J.
@Timwi: I'll post the code tonight. I did scratch my head for a bit to get this to work, but now it's being used in a high-volume production app without any problems.
Eric J.
+4  A: 

You need to use a "Named Mutex". There is a separate constructor that allows you to define a named mutex.

Nick
Yes a named Mutex is the way processes can share this windows primitive.
Preet Sangha
I did. It didn't work. Some working example code would be nice.
Timwi
@Timwi - Could you provide the code you are using which isn't working? There may be something else you are doing which is causing this to fail.
Nick
I suspect @Timwi might be trying to take the mutex multiple times, but only releasing it once.
Anon.
A: 

Although I would recommend you use Mutex and investigate whether or not the issue is your code itself, one very complex highly fragile alternative might be to use "lock files".

Basically, you create a temporary file in the file system with a name that both processes recognize. If the file exists, the lock is in place; the other process should release any other locks, wait, and attempt to re-acquire. If the file does not exist, the lock is not in place; the process should create the file to complete the lock and continue processing, deleting the file when releasing the lock.

As I said, it's very complex and fragile, but it doesn't use Mutex.

Alternatively, use Mutex.

Randolpho
A: 

If you don't want to use a Mutex, I'd say rolling your own using say a File that exists would a simple solution. If the file exists the don't start a new one. However you will have to handle the exception cases where a process terminates early and the file isn't cleaned up.

Going back to mutex for a moment, this is just a OS 'object' for want of a better term. You really need some like this on each OS you use. I'm sure that the other .net clr's will allow you access those system primitives too. I'd just wrap up each using a defined assembly that could be different on each platform.

Hope this makes sense.

Preet Sangha
+5  A: 

I have two applications:

ConsoleApplication1.cs

using System;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Mutex mutex = new Mutex(false, "AwesomeMutex");

            Console.WriteLine("ConsoleApplication1 created mutex, waiting . . .");

            mutex.WaitOne();

            Console.Write("Waiting for input. . .");
            Console.ReadKey(true);

            mutex.ReleaseMutex();
            Console.WriteLine("Disposed mutex");
        }
    }
}

ConsoleApplication2.cs

using System;
using System.Threading;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Mutex mutex = new Mutex(false, "AwesomeMutex");
            Console.WriteLine("ConsoleApplication2 Created mutex");

            mutex.WaitOne();

            Console.WriteLine("ConsoleApplication2 got signalled");

            mutex.ReleaseMutex();
        }
    }
}

Starting ConsoleApplication1, followed by ConsoleAplication2 works perfectly with no errors. If your code still bombs out, its a bug with your code, not the Mutex class.

Juliet
Many thanks. I could have sworn that this is exactly what I did, but I must have done something differently because it didn't work and now it does. Thanks again.
Timwi
A: 

Maybe I'm oversimplifying the problem, but I've just checked the process name in the past.

You could just use this function to wait until the other's done and then run the current process.

''' <summary>
''' Is this app already running as a standalone exe?
''' </summary>
''' <returns></returns>
''' <remarks>
''' NOTE: The .vshost executable runs the whole time Visual Studio is open, not just when debugging.
''' So for now we're only checking if it's running as a standalone.
''' </remarks>
public bool AlreadyRunning()
{
    const string VS_PROCESS_SUFFIX = ".vshost";

    //remove .vshost if present
    string processName = Process.GetCurrentProcess.ProcessName.Replace(VS_PROCESS_SUFFIX, string.Empty);
    
    int standaloneInstances = Process.GetProcessesByName(processName).Length;
    //Dim vsInstances As Integer = Process.GetProcessesByName(processName & VS_PROCESS_SUFFIX).Length
    
    //Return vsInstances + standaloneInstances > 1
    return standaloneInstances > 1;
}
Adam Neal
Your answer requires that I wait for the *entire process* to finish, making it irrelevant to my question.
Timwi