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

Sam*_*ron 365 c# concurrency mutex

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

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

一个会起作用的

  • 无论我的机器所在的语言环境如何
  • 保证正确释放互斥锁
  • 如果未获取互斥锁,则可选择不会永久挂起
  • 处理其他进程放弃互斥锁的情况

Sam*_*ron 393

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

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 提示:注意使用Mutex和ASP.NET:"Mutex类强制执行线程标识,因此互斥锁只能由获取它的线程释放.相比之下,Semaphore类不强制执行线程标识." ASP.NET请求可以由多个线程提供服务. (2认同)

dee*_*ee1 125

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

使用:

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

辅助类:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 超时0应该仍然是零的超时,而不是无限!更好地检查"<0"而不是"<= 0". (3认同)
  • 如何处理使用 SingleGlobalInstance 的类中的超时异常。在构造实例时抛出异常也是一种好习惯吗? (2认同)
  • @antistar:我发现在Dispose方法中使用`_mutex.Close()`代替`_mutex.Dispose()`对我有用.尝试处理底层的WaitHandle导致错误.`Mutex.Close()`处理底层资源. (2认同)

Van*_*yen 12

当在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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,此问题现已在接受的答案中修复. (7认同)

Lia*_*iam 10

如果另一个实例已在运行,此示例将在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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 8

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

另一种解决方案:

static class Program
{
    [STAThread]
    static void Main()
    {
        if (SingleApplicationDetector.IsRunning()) {
            return;
        }

        Application.Run(new MainForm());

        SingleApplicationDetector.Close();
    }
}
Run Code Online (Sandbox Code Playgroud)

而且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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

<< System.Threading.Mutex

Ref:Semaphore.OpenExisting()

  • `Semaphore.OpenExisting`和`new Semaphore`之间可能的竞争条件. (7认同)