Глобальний Mutex - це не лише забезпечити наявність лише одного примірника програми. Я особисто вважаю за краще використовувати Microsoft.VisualBasic для забезпечення застосунку для одного примірника, як описано у розділі Який правильний спосіб створити однофазовий додаток WPF?(Відповідь Дейла Рагана) ... Я виявив, що простіше передати аргументи, отримані під час запуску нової програми, до початкової програми для єдиного примірника.
Але стосовно деякого попереднього коду в цій темі, я вважаю за краще не створювати Mutex кожен раз, коли хочу мати замок на ньому. Це може бути добре для однієї програми, але в іншому використанні, як мені здається, є надмірною.
Ось чому я пропоную цю реалізацію замість цього:
Використання:
static MutexGlobal _globalMutex = null;
static MutexGlobal GlobalMutexAccessEMTP
{
get
{
if (_globalMutex == null)
{
_globalMutex = new MutexGlobal();
}
return _globalMutex;
}
}
using (GlobalMutexAccessEMTP.GetAwaiter())
{
...
}
Mutex Global Wrapper:
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();
}
}
// ************************************************************************
}
}
Офіціант
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
перевіркуcreatedNew
та додатиmutex.Dispose()
всерединуfinally
. Я не можу пояснити це чітко (не знаю причини) прямо зараз, але я потрапив у ситуацію, колиmutex.WaitOne
повернувсяtrue
післяcreatedNew
ставfalse
(я придбав мютекс у поточному,AppDomain
а потім завантажив новийAppDomain
і виконав той самий код від всередині нього).