What is a good pattern for using a Global Mutex in C#?

前端 未结 8 696
温柔的废话
温柔的废话 2020-11-21 23:16

The Mutex class is very misunderstood, and Global mutexes even more so.

What is good, safe pattern to use when creating Global mutexes?

One that will work

8条回答
  •  醉酒成梦
    2020-11-21 23:54

    A global Mutex is not only to ensure to have only one instance of an application. I personally prefer using Microsoft.VisualBasic to ensure single instance application like described in What is the correct way to create a single-instance WPF application? (Dale Ragan answer)... I found that's easier to pass arguments received on new application startup to the initial single instance application.

    But regarding some previous code in this thread, I would prefer to not create a Mutex each time I want to have a lock on it. It could be fine for a single instance application but in other usage it appears to me has overkill.

    That's why I suggest this implementation instead:

    Usage:

    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 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;
                }
            }
    
            // ************************************************************************
            /// 
            /// 
            /// 
            /// 
            /// 
            public MutexGlobalAwaiter GetAwaiter(int timeOut)
            {
                return new MutexGlobalAwaiter(this, timeOut);
            }
    
            // ************************************************************************
            /// 
            /// 
            /// 
            /// 
            /// 
            public MutexGlobalAwaiter GetAwaiter()
            {
                return new MutexGlobalAwaiter(this, DefaultTimeOut);
            }
    
            // ************************************************************************
            /// 
            /// 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. 
            /// 
            /// 
            /// 
            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();
                }
            }
    
            // ************************************************************************
    
        }
    }
    

    Awaiter

    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
        }
    }
    

提交回复
热议问题