Um Mutex global não serve apenas para garantir a existência de apenas uma instância de um aplicativo. Pessoalmente, prefiro usar o Microsoft.VisualBasic para garantir um aplicativo de instância única, como descrito em Qual é a maneira correta de criar um aplicativo WPF de instância única? (Resposta de Dale Ragan) ... Achei mais fácil passar argumentos recebidos na inicialização do novo aplicativo para o aplicativo de instância única inicial.
Mas, com relação a algum código anterior desse segmento, eu preferiria não criar um Mutex toda vez que eu quisesse ter um bloqueio nele. Pode ser bom para um aplicativo de instância única, mas em outro uso parece-me um exagero.
É por isso que sugiro esta implementação:
Uso:
static MutexGlobal _globalMutex = null;
static MutexGlobal GlobalMutexAccessEMTP
{
get
{
if (_globalMutex == null)
{
_globalMutex = new MutexGlobal();
}
return _globalMutex;
}
}
using (GlobalMutexAccessEMTP.GetAwaiter())
{
...
}
Empacotador Global da Mutex:
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
namespace HQ.Util.General.Threading
{
public class MutexGlobal : IDisposable
{
// ************************************************************************
public string Name { get; private set; }
internal Mutex Mutex { get; private set; }
public int DefaultTimeOut { get; set; }
public Func<int, bool> FuncTimeOutRetry { get; set; }
// ************************************************************************
public static MutexGlobal GetApplicationMutex(int defaultTimeOut = Timeout.Infinite)
{
return new MutexGlobal(defaultTimeOut, ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value);
}
// ************************************************************************
public MutexGlobal(int defaultTimeOut = Timeout.Infinite, string specificName = null)
{
try
{
if (string.IsNullOrEmpty(specificName))
{
Name = Guid.NewGuid().ToString();
}
else
{
Name = specificName;
}
Name = string.Format("Global\\{{{0}}}", Name);
DefaultTimeOut = defaultTimeOut;
FuncTimeOutRetry = DefaultFuncTimeOutRetry;
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
Mutex = new Mutex(false, Name, out bool createdNew, securitySettings);
if (Mutex == null)
{
throw new Exception($"Unable to create mutex: {Name}");
}
}
catch (Exception ex)
{
Log.Log.Instance.AddEntry(Log.LogType.LogException, $"Unable to create Mutex: {Name}", ex);
throw;
}
}
// ************************************************************************
/// <summary>
///
/// </summary>
/// <param name="timeOut"></param>
/// <returns></returns>
public MutexGlobalAwaiter GetAwaiter(int timeOut)
{
return new MutexGlobalAwaiter(this, timeOut);
}
// ************************************************************************
/// <summary>
///
/// </summary>
/// <param name="timeOut"></param>
/// <returns></returns>
public MutexGlobalAwaiter GetAwaiter()
{
return new MutexGlobalAwaiter(this, DefaultTimeOut);
}
// ************************************************************************
/// <summary>
/// This method could either throw any user specific exception or return
/// true to retry. Otherwise, retruning false will let the thread continue
/// and you should verify the state of MutexGlobalAwaiter.HasTimedOut to
/// take proper action depending on timeout or not.
/// </summary>
/// <param name="timeOutUsed"></param>
/// <returns></returns>
private bool DefaultFuncTimeOutRetry(int timeOutUsed)
{
// throw new TimeoutException($"Mutex {Name} timed out {timeOutUsed}.");
Log.Log.Instance.AddEntry(Log.LogType.LogWarning, $"Mutex {Name} timeout: {timeOutUsed}.");
return true; // retry
}
// ************************************************************************
public void Dispose()
{
if (Mutex != null)
{
Mutex.ReleaseMutex();
Mutex.Close();
}
}
// ************************************************************************
}
}
Um garçom
using System;
namespace HQ.Util.General.Threading
{
public class MutexGlobalAwaiter : IDisposable
{
MutexGlobal _mutexGlobal = null;
public bool HasTimedOut { get; set; } = false;
internal MutexGlobalAwaiter(MutexGlobal mutexEx, int timeOut)
{
_mutexGlobal = mutexEx;
do
{
HasTimedOut = !_mutexGlobal.Mutex.WaitOne(timeOut, false);
if (! HasTimedOut) // Signal received
{
return;
}
} while (_mutexGlobal.FuncTimeOutRetry(timeOut));
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_mutexGlobal.Mutex.ReleaseMutex();
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~MutexExAwaiter()
// {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
}
using
para verificarcreatedNew
e adicionarmutex.Dispose()
dentrofinally
. Não posso explicar claramente (não sei o motivo) no momento, mas me deparei com uma situação em quemutex.WaitOne
retorneitrue
depois que mecreatedNew
torneifalse
(adquiri o mutex no atualAppDomain
e carreguei um novoAppDomain
e executei o mesmo código de dentro dele).