Reputation: 81
I need a global mutex to ensure only one instance of my application can run at a time.
I found the following code on SO:
using var mutex = MutexAcl.Create(true, mutexId, out bool mutexCreated, securitySettings);
var hasHandle = false;
try
{
try
{
hasHandle = mutex.WaitOne(1000, false);
if (hasHandle == false)
LogError("Timeout waiting for exclusive access.");
}
catch (AbandonedMutexException)
{
hasHandle = true;
}
// do work
}
finally
{
if (hasHandle)
mutex.ReleaseMutex();
}
but something vexes me:
Why wait on the mutex when you always create it with initiallyOwned = true
. Can't we use the mutexCreated
variable in such a case?
If I understand it correctly mutexCreated
is true
if the global mutex did not exist and it was created with that method call and, since initiallyOwned = true
, it is owned by the thread and if mutexCreated
is false
then it already existed and the current thread does not own it, so it's safe to assume another instance is already running.
Am I missing something? Why do we need to wait on the mutex we already have a call to Create
for?
Upvotes: 0
Views: 80
Reputation: 59207
You are passing true
as the first argument (initiallyOwned
). Thus you assume that the Mutex is already owned after you got it.
However, the documentation says (emphasis mine):
initiallyOwned Boolean
true to give the calling thread initial ownership of the named system mutex if the named system mutex is created as a result of this call; otherwise, false.
So, you only have ownership if the Mutex was created. If the Mutex was not created, but already existed, you don't have ownership. But you still have the Mutex.
So, if you pass true
for initiallyOwned
and createdNew
was also set to true
as a result of the call, then you don't need to wait.
You can not get rid of the WaitOne()
call, because this call is the one that may cause the AbandonedMutexException
and thus your single-instance app should still start.
Therefore, you could rewrite the code like this:
using var mutex = MutexAcl.Create(true, mutexId, out bool mutexCreated, securitySettings);
var hasHandle = false;
try
{
try
{
if (mutexCreated) // <-- new code
{
hasHandle = true;
}
else /* mutex already existed */
{
hasHandle = mutex.WaitOne(1000, false);
if (hasHandle == false)
LogError("Timeout waiting for exclusive access.");
}
}
catch (AbandonedMutexException)
{
hasHandle = true;
}
// do work
}
finally
{
if (hasHandle)
mutex.ReleaseMutex();
}
But why? It only makes the code longer and harder to understand. If you call WaitOne()
in every case, it's clear that you have access to the Mutex. No need of thinking.
Is creating a mutex the same as waiting on it?
No. Creating a Mutex might have included the waiting (see the initiallyOwned
parameter), but not necessarily. Also, you can wait on a Mutex without creating it (see the WaitOne()
method).
Let's use the following code to demonstrate the problem that you might get when you don't use WaitOne():
#pragma warning disable CA1416
using System.Security.AccessControl;
using System.Security.Principal;
var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
var allowEveryoneRule = new MutexAccessRule(everyone, MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
using var mutex = MutexAcl.Create(true, "singleInstanceAppDemo", out bool mutexCreated, securitySettings);
if (!mutexCreated)
{
Console.WriteLine("This is a single instance app.");
Console.WriteLine("Press Enter to confirm this.");
Console.ReadLine();
return;
}
Console.WriteLine("You are the single user.");
Console.WriteLine("Press Enter when you're done working.");
Console.ReadLine();
mutex.ReleaseMutex();
Now do the following:
#pragma warning disable CA1416
using System.Security.AccessControl;
using System.Security.Principal;
var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
var allowEveryoneRule = new MutexAccessRule(everyone, MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
using var mutex = MutexAcl.Create(true, "singleInstanceAppDemo", out bool mutexCreated, securitySettings);
try
{
bool success = mutex.WaitOne(1, false);
if (!success)
{
Console.WriteLine("This is a single instance app.");
Console.WriteLine("Press Enter to confirm this.");
Console.ReadLine();
return;
}
Console.WriteLine("You are the single user.");
Console.WriteLine("Press Enter when you're done working.");
Console.ReadLine();
mutex.ReleaseMutex();
}
catch (AbandonedMutexException)
{
Console.WriteLine("Lucky!");
Console.WriteLine("Press Enter when you're done working.");
Console.ReadLine();
mutex.ReleaseMutex();
}
// TODO: use try/finally to release the Mutex.
If you repeat the experiment with this code, the output of step 5 will be "Lucky!".
Upvotes: 3