How to prevent two instances of an application from doing the same thing at the same time?
Asked Answered
A

7

9

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.

Arlinda answered 24/2, 2010 at 21:57 Comment(4)
You're using .NET, and complaining about Mutex being windows-specific?Rhoads
@Anon.: mono-project.com/Main_PageUnique
@Randolpho: Who's goal is to implement the entire .NET library. Including System.Threading.Mutex. If it's part of the .NET library, you can expect it to run on any .NET platform (including mono, once it finishes implementing that part of it).Rhoads
Oh, I know. I was just sayin'Unique
M
6

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.

Manakin answered 24/2, 2010 at 22:21 Comment(1)
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.Arlinda
I
4

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;
        }
    }
}
Illene answered 24/2, 2010 at 22:8 Comment(2)
@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.Illene
the finally could crash though.. if the mutex is null then close wouldn't work.. I'm not sure how it could be null, but if it can't then the test for null wouldn't be needed.Henpeck
A
4

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

Adjudication answered 24/2, 2010 at 22:8 Comment(4)
Yes a named Mutex is the way processes can share this windows primitive.Commodore
I did. It didn't work. Some working example code would be nice.Arlinda
@Arlinda - 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.Adjudication
I suspect @Arlinda might be trying to take the mutex multiple times, but only releasing it once.Rhoads
U
0

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.

Unique answered 24/2, 2010 at 22:14 Comment(0)
C
0

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.

Commodore answered 24/2, 2010 at 22:14 Comment(0)
H
0

I had to do this in a forms app, simple but works fine.

static void Main()
{ 

var processes = Process.GetProcesses().ToList();
            int count = 0;
            foreach (var item in processes)
            {
                if (item.ProcessName == "NameOfYourProcess")
                {
                    count++;
                }
                if(count > 1) 
                {
                    Environment.Exit(0);
                }
            }
}
Helpless answered 15/12, 2022 at 15:53 Comment(0)
C
-1

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;
}
Cavazos answered 24/2, 2010 at 22:31 Comment(1)
Your answer requires that I wait for the entire process to finish, making it irrelevant to my question.Arlinda

© 2022 - 2024 — McMap. All rights reserved.