The Misunderstood Mutex

LINK: 

Someone should delete this article in purgatory at codeproject.com. The article is full of mis-information, but look at the view count. 

The article attempts to restrict a Windows application to a single running instance. The article tries to do this using the Process class from the System.Diagnostics namespace. The code invokes Process.GetProcessesByName(processName) to see if there are any existing processes running with the same name, and exits if another is found. If you do some searching, you’ll find other code snippets using the same technique.

There are at least three problems with this approach:

1) It doesn’t account for race conditions. Two instances of the application could launch at nearly the same time, see each other, and both shut down.

2) It doesn’t work in terminal services, at least not if I want the application to run an instance in each login session.

3) It doesn’t account for the possibility that someone else might have a process with the same name.

These problems might be considered ‘edge conditions’, except there is an easy, foolproof way to check for a running instance of an application. A named mutex allows multiple processes to use the same mutex object for interprocess synchronization. The author asserts a mutex is not safe on a multiprocessor machine. If this were true it would be the end of civilization as we know it.

The name of the mutex will preferably be a unique identifier to offset the chances of another application using the same name. One could chose to use the full name of the executing assembly, or a GUID. If the application can acquire the named mutex with WaitOne, then it is the first instance running. If the application calls WaitOne with a timeout value and WaitOne returns false, another instance of the application is running and this one needs to exit.

When using this approach in .NET there is one ‘gotcha’. The following code has a small problem:

[STAThread]
static void Main()
{
Mutex mutex = new Mutex(false, appGuid);
if(!mutex.WaitOne(0, false))
{
MessageBox.Show("Instance already running");
return;
}
Application.Run(new Form1());
}
private static string appGuid = "c0a76b5a-12ab-45c5-b9d9-d693faa6e7b9";

The problem is easy to reproduce if you run the following code in a release build:

[STAThread]
static void Main()
{
Mutex mutex = new Mutex(false, appGuid);
if(!mutex.WaitOne(0, false))
{
MessageBox.Show("Instance already running");
return;
}
GC.Collect();
Application.Run(new Form1());
}
private static string appGuid = "c0a76b5a-12ab-45c5-b9d9-d693faa6e7b9";

Since the mutex goes unused when the Form starts running, the compiler and garbage collector are free to conspire together to collect the mutex out of existence. After the first garbage collector run, one might be able to launch multiple instances of the application again. The following code will keep the mutex alive. (The call to GC.Collect is still here just for testing).

[STAThread]
static void Main()
{
Mutex mutex = new Mutex(false, appGuid);
if(!mutex.WaitOne(0, false))
{
MessageBox.Show("Instance already running");
return;
}

GC.Collect();
Application.Run(new Form1());
GC.KeepAlive(mutex);
}
private static string appGuid = "c0a76b5a-12ab-45c5-b9d9-d693faa6e7b9";

There is still an imperfection in the code. Mutex derives from WaitHandle, and WaitHandle implements IDisposable. Here is one more example that keeps the mutex alive and properly disposes the mutex when finished.

[STAThread]
static void Main()
{
using(Mutex mutex = new Mutex(false, appGuid))
{
if(!mutex.WaitOne(0, false))
{
MessageBox.Show("Instance already running");
return;
}

GC.Collect();
Application.Run(new Form1());
}
}

With the above code I can run the application from the console, and also log into the machine with terminal services and run the application in a different session. Terminal services provides a unique namespace for each client session (so does fast user switching on Windows XP). When I create a named mutex, the mutex lives inside the namespace for the session I am running in. Like .NET namespaces, terminal services uses namespaces to prevent naming collisions.

If I want to have only one instance of the application running across all sessions on the machine, I can put the named mutex into the global namespace with the prefix “Global\”.

[STAThread]
static void Main()
{
using(Mutex mutex = new Mutex(false, @"Global\" + appGuid))
{
if(!mutex.WaitOne(0, false))
{
MessageBox.Show("Instance already running");
return;
}

GC.Collect();
Application.Run(new Form1());
}
}

After all this you may find you need to adjust permissions on the mutex in order to access the mutex from another process running with different credentials than the first. This requires some PInvoke work and deserves a post unto itself.

原文地址:https://www.cnblogs.com/LeoWong/p/2041962.html