C #에서 Global Mutex를 사용하기위한 좋은 패턴은 무엇입니까?


377

뮤텍스 클래스는 매우 오해되었으며 글로벌 뮤텍스는 훨씬 더 그렇습니다.

글로벌 뮤텍스를 만들 때 사용하는 안전하고 좋은 패턴은 무엇입니까?

작동 할 것

  • 내 컴퓨터가있는 로캘에 관계없이
  • 뮤텍스를 올바르게 해제합니다
  • 뮤텍스를 얻지 못하면 선택적으로 영원히 멈추지 않습니다.
  • 다른 프로세스가 뮤텍스를 버리는 경우를 처리

답변:


402

제대로 이해하기가 어렵 기 때문에 이것이 밖에 있는지 확인하고 싶습니다.

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
내부 using를 확인 createdNew하고 추가 하지 않을 수 mutex.Dispose()있습니다 finally. 나는 그것을 명확하게 설명 할 수 없다 (이유를 모른다). 그러나 mutex.WaitOne돌아온 true후에 상황에 처 createdNew하게되었다. false(현재의 뮤텍스를 얻은 AppDomain다음 새로운 AppDomain코드 를로드하고 그 안에).
Sergey.quixoticaxis.Ivanov

1. exitContext = false무엇을 mutex.WaitOne(5000, false)합니까? CoreCLR , 2 에서만 어설 션을 유발할 수있는 것 같습니다 . 누군가가 궁금하다면, Mutex생성자에서 그 이유 initiallyOwned이 MSDN 기사에false 부분적으로 설명되어 있습니다.
jrh

3
팁 : ASP.NET에서 Mutex 사용에주의하십시오. "Mutex 클래스는 스레드 ID를 적용하므로 뮤텍스는이를 획득 한 스레드에서만 릴리스 할 수 있습니다. 반면 Semaphore 클래스는 스레드 ID를 강제하지 않습니다." 여러 스레드에서 ASP.NET 요청을 처리 할 수 ​​있습니다.
Sam Rueby

VB.NET에서 startupnextinstance 이벤트를 안전하게? C # docs.microsoft.com/es-es/dotnet/api/에 없음
Kiquenet

WaitOne을 사용하지 않고 내 대답을 참조하십시오. stackoverflow.com/a/59079638/4491768
Wouter

129

허용 된 답변을 사용하여 Locker 문을 사용할 때와 비슷한 방식으로 사용할 수 있도록 도우미 클래스를 만듭니다. 내가 공유 할 줄 ​​알았는데

사용하다:

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

정말 고마워요! 참고 : 코드 분석 중 CA2213 경고를 방지하기 위해 위의 Dispose 메소드를 업데이트했습니다. 나머지는 잘 통과했습니다. 자세한 내용은 msdn.microsoft.com/query/…를
Pat Hermens

1
SingleGlobalInstance를 사용하는 클래스에서 시간 초과 예외를 처리하는 방법 또한 인스턴스를 구성하는 동안 예외를 발생시키는 것이 좋습니다?
kiran

3
타임 아웃 0은 무한대가 아니라 타임 아웃 0이어야합니다! < 0대신 확인하는 것이 좋습니다 <= 0.
ygoe

2
@ antistar : Dispose 메서드 _mutex.Close()대신 사용하는 것이 나에게 효과적이라는 것을 알았 _mutex.Dispose()습니다. 기본 WaitHandle을 삭제하려고 시도하여 오류가 발생했습니다. Mutex.Close()기본 리소스를 폐기합니다.
djpMusic

1
"AppName이 작동을 멈췄습니다."가 표시됩니다. 앱의 두 번째 인스턴스를 열려고 할 때. 사용자가 앱의 두 번째 인스턴스를 열려고 할 때 앱에 초점을 맞추고 싶습니다. 어떻게하니?
Bhaskar

13

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

8
이 문제는 이제 승인 된 답변에서 수정되었습니다.
Van Nguyen

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

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 대신 세마포어를 사용해야하는 이유 :

Mutex 클래스는 스레드 ID를 적용하므로 뮤텍스는이를 획득 한 스레드에서만 해제 할 수 있습니다. 반대로 Semaphore 클래스는 스레드 ID를 적용하지 않습니다.

<< System.Threading.Mutex

참조 : 세마포어 OpenExisting ()


7
사이 가능한 경쟁 조건 Semaphore.OpenExistingnew Semaphore.
xmedeko 2016 년

3

때로는 모범으로 배우는 것이 가장 도움이됩니다. 이 콘솔 응용 프로그램을 세 개의 다른 콘솔 창에서 실행하십시오. 먼저 실행 한 응용 프로그램이 먼저 뮤텍스를 획득하고 다른 두 응용 프로그램이 차례를 기다리는 것을 볼 수 있습니다. 그런 다음 첫 번째 응용 프로그램에서 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
        }
    }
}

여기에 이미지 설명을 입력하십시오


0

글로벌 Mutex는 하나의 응용 프로그램 인스턴스 만 갖도록 보장 할 수 없습니다. 개인적으로 Microsoft.VisualBasic 을 사용하여 단일 인스턴스 WPF 응용 프로그램을 만드는 올바른 방법은 무엇입니까?(Dale Ragan 답변) ... 새로운 응용 프로그램 시작시 수신 된 인수를 초기 단일 인스턴스 응용 프로그램으로 전달하는 것이 더 쉽다는 것을 알았습니다.

그러나이 스레드의 이전 코드와 관련하여 잠금을 원할 때마다 Mutex를 생성하지 않는 것이 좋습니다. 단일 인스턴스 응용 프로그램에는 문제가 없지만 다른 사용법에서는 지나치게 많은 것으로 보입니다.

그렇기 때문에 대신이 구현을 제안합니다.

용법:

static MutexGlobal _globalMutex = null;
static MutexGlobal GlobalMutexAccessEMTP
{
    get
    {
        if (_globalMutex == null)
        {
            _globalMutex = new MutexGlobal();
        }
        return _globalMutex;
    }
}

using (GlobalMutexAccessEMTP.GetAwaiter())
{
    ...
}   

뮤텍스 글로벌 래퍼 :

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

0

AbandonedMutexException이 발생할 수 있으므로 WaitOne이없는 솔루션 (WPF 용)입니다. 이 솔루션은 createdNew 부울을 반환하는 Mutex 생성자를 사용하여 뮤텍스가 이미 생성되었는지 확인합니다. 또한 GetType (). GUID를 사용하므로 실행 파일 이름을 바꾸면 여러 인스턴스가 허용되지 않습니다.

글로벌 대 로컬 뮤텍스는 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();
    }
}

Mutex는 IDisposable을 구현하기 때문에 자동으로 해제되지만 완벽하게 호출하려면 다음을 처리하십시오.

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

모든 것을 기본 클래스로 옮기고 허용 된 답변에서 allowEveryoneRule을 추가하십시오. 또한 OS에 의해 자동으로 릴리스되기 때문에 실제로 필요한 것처럼 보이지 않지만 ReleaseMutex를 추가했습니다 (응용 프로그램이 충돌하고 ReleaseMutex를 호출하지 않으면 재부팅해야합니까?).

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();
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.