首页 文章

在C#中使用Global Mutex有什么好的模式?

提问于
浏览
353

Mutex类被误解了,而Global mutex则更是如此 .

在创建全局互斥锁时使用什么是好的,安全的模式?

一个会起作用的

  • 无论我的机器所在的语言环境如何

  • 保证正确释放互斥锁

  • 如果未获取互斥锁,则可选择不会永久挂起

  • 处理其他进程放弃互斥锁的情况

6 回答

  • 379

    我想确保它在那里,因为它很难做到:

    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();
            }
        }
    }
    
  • 1

    使用接受的答案我创建了一个帮助器类,因此您可以使用与使用Lock语句类似的方式使用它 . 只是想我会分享 .

    使用:

    using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock
    {
        //Only 1 of these runs at a time
        RunSomeStuff();
    }
    

    辅助类:

    class SingleGlobalInstance : IDisposable
    {
        //edit by user "jitbit" - renamed private fields to "_"
        public bool _hasHandle = false;
        Mutex _mutex;
    
        private void InitMutex()
        {
            string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value;
            string mutexId = string.Format("Global\\{{{0}}}", appGuid);
            _mutex = new Mutex(false, mutexId);
    
            var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
            var securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);
            _mutex.SetAccessControl(securitySettings);
        }
    
        public SingleGlobalInstance(int timeOut)
        {
            InitMutex();
            try
            {
                if(timeOut < 0)
                    _hasHandle = _mutex.WaitOne(Timeout.Infinite, false);
                else
                    _hasHandle = _mutex.WaitOne(timeOut, false);
    
                if (_hasHandle == false)
                    throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
            }
            catch (AbandonedMutexException)
            {
                _hasHandle = true;
            }
        }
    
    
        public void Dispose()
        {
            if (_mutex != null)
            {
                if (_hasHandle)
                    _mutex.ReleaseMutex();
                _mutex.Close();
            }
        }
    }
    
  • 8

    如果另一个实例已在运行,此示例将在5秒后退出 .

    // 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();
            }
        }
    }
    
  • 10

    当在2个不同用户下运行的2个进程同时尝试初始化互斥锁时,在接受的答案中存在竞争条件 . 在第一个进程初始化互斥锁之后,如果第二个进程在第一个进程将访问规则设置为每个进程之前尝试初始化互斥锁,则第二个进程将抛出未经授权的异常 .

    请参阅下面的更正答案:

    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();
            }
        }
    }
    
  • 10

    Mutex和WinApi CreateMutex()都不适合我 .

    另一种解决方案:

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

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

    使用信号量而不是互斥锁的原因:

    Mutex类强制执行线程标识,因此互斥锁只能由获取它的线程释放 . 相比之下,Semaphore类不强制执行线程标识 . << System.Threading.Mutex

    参考:Semaphore.OpenExisting()

  • 115

    有时通过实例学习最有帮助 . 在三个不同的控制台窗口中运行此控制台应用您将看到您首先运行的应用程序首先获取互斥锁,而其他两个正在等待它们 . 然后在第一个应用程序中按Enter键,您将看到应用程序2现在通过获取互斥锁继续运行,但应用程序3正在等待轮到它 . 在应用程序2中按Enter后,您将看到应用程序3继续 . 这说明了一个互斥锁的概念,它保护一段代码只能由一个线程(在这种情况下是一个进程)执行,就像写一个文件一样 .

    using System;
    using System.Threading;
    
    namespace MutexExample
    {
        class Program
        {
            static Mutex m = new Mutex(false, "myMutex");//create a new NAMED mutex, DO NOT OWN IT
            static void Main(string[] args)
            {
                Console.WriteLine("Waiting to acquire Mutex");
                m.WaitOne(); //ask to own the mutex, you'll be queued until it is released
                Console.WriteLine("Mutex acquired.\nPress enter to release Mutex");
                Console.ReadLine();
                m.ReleaseMutex();//release the mutex so other processes can use it
            }
        }
    }
    

    enter image description here

相关问题