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

前端 未结 8 687
温柔的废话
温柔的废话 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:45

    This example will exit after 5 seconds if another instance is already running.

    // unique id for global mutex - Global prefix means it is global to the machine
    const string mutex_id = "Global\\{B1E7934A-F688-417f-8FCB-65C3985E9E27}";
    
    static void Main(string[] args)
    {
    
        using (var mutex = new Mutex(false, mutex_id))
        {
            try
            {
                try
                {
                    if (!mutex.WaitOne(TimeSpan.FromSeconds(5), false))
                    {
                        Console.WriteLine("Another instance of this program is running");
                        Environment.Exit(0);
                    }
                }
                catch (AbandonedMutexException)
                {
                    // Log the fact the mutex was abandoned in another process, it will still get aquired
                }
    
                // Perform your work here.
            }
            finally
            {
                mutex.ReleaseMutex();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:51

    I want to make sure this is out there, because it's so hard to get right:

    using System.Runtime.InteropServices;   //GuidAttribute
    using System.Reflection;                //Assembly
    using System.Threading;                 //Mutex
    using System.Security.AccessControl;    //MutexAccessRule
    using System.Security.Principal;        //SecurityIdentifier
    
    static void Main(string[] args)
    {
        // get application GUID as defined in AssemblyInfo.cs
        string appGuid =
            ((GuidAttribute)Assembly.GetExecutingAssembly().
                GetCustomAttributes(typeof(GuidAttribute), false).
                    GetValue(0)).Value.ToString();
    
        // unique id for global mutex - Global prefix means it is global to the machine
        string mutexId = string.Format( "Global\\{{{0}}}", appGuid );
    
        // Need a place to store a return value in Mutex() constructor call
        bool createdNew;
    
        // edited by Jeremy Wiebe to add example of setting up security for multi-user usage
        // edited by 'Marc' to work also on localized systems (don't use just "Everyone") 
        var allowEveryoneRule =
            new MutexAccessRule( new SecurityIdentifier( WellKnownSidType.WorldSid
                                                       , null)
                               , MutexRights.FullControl
                               , AccessControlType.Allow
                               );
        var securitySettings = new MutexSecurity();
        securitySettings.AddAccessRule(allowEveryoneRule);
    
       // edited by MasonGZhwiti to prevent race condition on security settings via VanNguyen
        using (var mutex = new Mutex(false, mutexId, out createdNew, securitySettings))
        {
            // edited by acidzombie24
            var hasHandle = false;
            try
            {
                try
                {
                    // note, you may want to time out here instead of waiting forever
                    // edited by acidzombie24
                    // mutex.WaitOne(Timeout.Infinite, false);
                    hasHandle = mutex.WaitOne(5000, false);
                    if (hasHandle == false)
                        throw new TimeoutException("Timeout waiting for exclusive access");
                }
                catch (AbandonedMutexException)
                {
                    // Log the fact that the mutex was abandoned in another process,
                    // it will still get acquired
                    hasHandle = true;
                }
    
                // Perform your work here.
            }
            finally
            {
                // edited by acidzombie24, added if statement
                if(hasHandle)
                    mutex.ReleaseMutex();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:51

    There is a race condition in the accepted answer when 2 processes running under 2 different users trying to initialize the mutex at the same time. After the first process initializes the mutex, if the second process tries to initialize the mutex before the first process sets the access rule to everyone, an unauthorized exception will be thrown by the second process.

    See below for corrected answer:

    using System.Runtime.InteropServices;   //GuidAttribute
    using System.Reflection;                //Assembly
    using System.Threading;                 //Mutex
    using System.Security.AccessControl;    //MutexAccessRule
    using System.Security.Principal;        //SecurityIdentifier
    
    static void Main(string[] args)
    {
        // get application GUID as defined in AssemblyInfo.cs
        string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
    
        // unique id for global mutex - Global prefix means it is global to the machine
        string mutexId = string.Format( "Global\\{{{0}}}", appGuid );
    
        bool createdNew;
            // edited by Jeremy Wiebe to add example of setting up security for multi-user usage
            // edited by 'Marc' to work also on localized systems (don't use just "Everyone") 
            var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
            var securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);
    
            using (var mutex = new Mutex(false, mutexId, out createdNew, securitySettings))
            {
    
            // edited by acidzombie24
            var hasHandle = false;
            try
            {
                try
                {
                    // note, you may want to time out here instead of waiting forever
                    // edited by acidzombie24
                    // mutex.WaitOne(Timeout.Infinite, false);
                    hasHandle = mutex.WaitOne(5000, false);
                    if (hasHandle == false)
                        throw new TimeoutException("Timeout waiting for exclusive access");
                }
                catch (AbandonedMutexException)
                {
                    // Log the fact the mutex was abandoned in another process, it will still get aquired
                    hasHandle = true;
                }
    
                // Perform your work here.
            }
            finally
            {
                // edited by acidzombie24, added if statemnet
                if(hasHandle)
                    mutex.ReleaseMutex();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:52

    Neither Mutex nor WinApi CreateMutex() works for me.

    An alternate solution:

    static class Program
    {
        [STAThread]
        static void Main()
        {
            if (SingleApplicationDetector.IsRunning()) {
                return;
            }
    
            Application.Run(new MainForm());
    
            SingleApplicationDetector.Close();
        }
    }
    

    And the SingleApplicationDetector:

    using System;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Security.AccessControl;
    using System.Threading;
    
    public static class SingleApplicationDetector
    {
        public static bool IsRunning()
        {
            string guid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
            var semaphoreName = @"Global\" + guid;
            try {
                __semaphore = Semaphore.OpenExisting(semaphoreName, SemaphoreRights.Synchronize);
    
                Close();
                return true;
            }
            catch (Exception ex) {
                __semaphore = new Semaphore(0, 1, semaphoreName);
                return false;
            }
        }
    
        public static void Close()
        {
            if (__semaphore != null) {
                __semaphore.Close();
                __semaphore = null;
            }
        }
    
        private static Semaphore __semaphore;
    }
    

    Reason to use Semaphore instead of Mutex:

    The Mutex class enforces thread identity, so a mutex can be released only by the thread that acquired it. By contrast, the Semaphore class does not enforce thread identity.

    << System.Threading.Mutex

    Ref: Semaphore.OpenExisting()

    0 讨论(0)
  • 2020-11-21 23:53

    A solution (for WPF) without WaitOne because it can cause an AbandonedMutexException. This solution uses the Mutex constructor that returns the createdNew boolean to check if the mutex is already created. It also uses the GetType().GUID so renaming an executable doesn't allow multiple instances.

    Global vs local mutex see note in: https://docs.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=netframework-4.8

    private Mutex mutex;
    private bool mutexCreated;
    
    public App()
    {
        string mutexId = $"Global\\{GetType().GUID}";
        mutex = new Mutex(true, mutexId, out mutexCreated);
    }
    
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        if (!mutexCreated)
        {
            MessageBox.Show("Already started!");
            Shutdown();
        }
    }
    

    Because Mutex implements IDisposable it is released automatically but for completeness call dispose:

    protected override void OnExit(ExitEventArgs e)
    {
        base.OnExit(e);
        mutex.Dispose();
    }
    

    Move everything into a base class and add the allowEveryoneRule from the accepted answer. Also added ReleaseMutex though it doesn't look like it's really needed because it is released automatically by the OS (what if the application crashes and never calls ReleaseMutex would you need to reboot?).

    public class SingleApplication : Application
    {
        private Mutex mutex;
        private bool mutexCreated;
    
        public SingleApplication()
        {
            string mutexId = $"Global\\{GetType().GUID}";
    
            MutexAccessRule allowEveryoneRule = new MutexAccessRule(
                new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                MutexRights.FullControl, 
                AccessControlType.Allow);
            MutexSecurity securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);
    
            // initiallyOwned: true == false + mutex.WaitOne()
            mutex = new Mutex(initiallyOwned: true, mutexId, out mutexCreated, securitySettings);        
        }
    
        protected override void OnExit(ExitEventArgs e)
        {
            base.OnExit(e);
            if (mutexCreated)
            {
                try
                {
                    mutex.ReleaseMutex();
                }
                catch (ApplicationException ex)
                {
                    MessageBox.Show(ex.Message, ex.GetType().FullName, MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            mutex.Dispose();
        }
    
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            if (!mutexCreated)
            {
                MessageBox.Show("Already started!");
                Shutdown();
            }
        }
    }
    
    0 讨论(0)
  • 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<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();
                }
            }
    
            // ************************************************************************
    
        }
    }
    

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题