C # bir Global Mutex kullanmak için iyi bir model nedir?


377

Mutex sınıfı çok yanlış anlaşılmıştır ve Global muteksler daha da fazladır.

Global muteksler oluştururken kullanılacak iyi ve güvenli model nedir?

Biri çalışacak

  • Yerel ayardan bağımsız olarak, makinemin bulunduğu konum
  • Muteksi düzgün bir şekilde serbest bırakması garanti edilir
  • Muteks alınmazsa isteğe bağlı olarak sonsuza kadar askıda kalmaz
  • Diğer süreçlerin muteksi terk ettiği durumlarla ilgilenir

Yanıtlar:


402

Bunun dışarıda olduğundan emin olmak istiyorum, çünkü doğru olmak çok zor:

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
usingkontrol etmeyi createdNewve mutex.Dispose()içine eklemeyi ihmal etmek isteyebilirsiniz finally. Ben açıkça bunu açıklayamam (nedenini bilmiyorum) şu anda ama ben bir duruma kendim var mutex.WaitOnedöndü truesonra createdNewoldu false(Geçerli Muteksleri edinilen AppDomainve daha sonra yüklenen yeni AppDomainve aynı kod yürütülür içinde).
Sergey.quixoticaxis.Ivanov

1. İçinde exitContext = falseherhangi bir şey var mutex.WaitOne(5000, false)mı? Sadece CoreCLR bir assert neden olabilir gibi görünüyor herkes en merak ederse de, 2. Mutex'ın yapıcı, nedeni initiallyOwnedise falsekısmen açıklanabilir bu MSDN makalesinde .
jrh

3
Bir ipucu: Mutex'i ASP.NET ile kullanırken dikkat edin: "Mutex sınıfı iş parçacığı kimliğini zorlar, böylece bir muteks yalnızca onu alan iş parçacığı tarafından serbest bırakılabilir. Aksine, Semaphore sınıfı iş parçacığı kimliğini zorlamaz.". ASP.NET isteğine birden çok iş parçacığı tarafından hizmet verilebilir.
Sam Rueby

VB.NET'te startupnextinstance olayı güvenli mi? C # içinde değil docs.microsoft.com/es-es/dotnet/api/…
Kiquenet

WaitOne kullanmadan cevabımı görün. stackoverflow.com/a/59079638/4491768
Wouter

129

Kabul edilen cevabı kullanarak, bir Lock sınıfı kullanırsınız gibi bir yardımcı sınıf oluşturmak. Sadece paylaşacağımı düşündüm.

kullanın:

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

Ve yardımcı sınıf:

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

Harika iş, teşekkürler! FYI: Kod Analizi sırasında CA2213 uyarısını önlemek için yukarıdaki Dispose yöntemini güncelledim. Gerisi iyi geçti. Daha fazla ayrıntı için msdn.microsoft.com/query/…
adresini ziyaret

1
SingleGlobalInstance tüketen sınıfta zaman aşımı istisnasını nasıl işleyebilirim. Ayrıca, bir örnek oluştururken istisna atmak iyi bir uygulama mı?
kiran

3
0 zaman aşımı, sonsuz değil, yine de sıfır zaman aşımı olmalıdır! < 0Bunun yerine daha iyi kontrol edin <= 0.
ygoe

2
@antistar: Dispose yönteminde kullanmak _mutex.Close()yerine _mutex.Dispose()benim için çalıştı buldum. Hata, temeldeki WaitHandle'ı atmaya çalışılarak ortaya çıkmıştır. Mutex.Close()temel kaynakların tasfiyesi.
djpMusic

1
"AppName çalışmayı durdurdu" gösteriyor. uygulamanın ikinci bir örneğini açmaya çalıştığımda. Kullanıcı uygulamanın ikinci örneğini açmaya çalıştığında odağı odaklamak istiyorum. Nasıl yapabilirim?
Bhaskar

13

Muteksi aynı anda başlatmaya çalışan 2 farklı kullanıcı altında çalışan 2 işlem kabul edildiğinde bir yanıt koşulu vardır. İlk işlem muteksi başlattıktan sonra, ikinci işlem ilk işlemi erişim kuralını herkese ayarlamadan önce muteksi başlatmaya çalışırsa, ikinci işlem tarafından yetkisiz bir istisna atılır.

Düzeltilmiş cevap için aşağıya bakın:

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
Bu sorunun artık kabul edilen cevapta giderildiğini unutmayın.
Van Nguyen

10

Başka bir örnek zaten çalışıyorsa bu örnek 5 saniye sonra çıkacaktır.

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

Ne Mutex ne de WinApi CreateMutex () benim için çalışmıyor.

Alternatif bir çözüm:

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

        Application.Run(new MainForm());

        SingleApplicationDetector.Close();
    }
}

Ve 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 yerine Semaphore kullanma nedeni:

Mutex sınıfı iş parçacığı kimliğini zorlar, böylece bir muteks yalnızca onu alan iş parçacığı tarafından serbest bırakılabilir. Buna karşılık, Semaphore sınıfı iş parçacığı kimliğini zorlamaz.

<< System.Threading.Mutex

Referans: Semaphore.OpenExisting ()


7
Arasındaki olası yarış koşulları Semaphore.OpenExistingve new Semaphore.
xmedeko

3

Bazen örnek olarak öğrenmek en çok yardımcı olur. Bu konsol uygulamasını üç farklı konsol penceresinde çalıştırın. İlk çalıştırdığınız uygulamanın önce muteksi aldığını görürsünüz, diğer ikisi ise sıralarını bekler. Daha sonra ilk uygulamada enter tuşuna basın, mutex'i alarak uygulama 2'nin artık çalışmaya devam ettiğini göreceksiniz, ancak uygulama 3 sırasını bekliyor. Uygulama 2'de enter tuşuna bastıktan sonra uygulama 3'ün devam ettiğini göreceksiniz. Bu, bir dosyaya örnek olarak yazmak gibi yalnızca bir iş parçacığı (bu durumda bir işlem) tarafından yürütülecek kod bölümünü koruyan bir muteks kavramını gösterir.

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

resim açıklamasını buraya girin


0

Global Mutex yalnızca bir uygulamanın tek bir örneğinin olmasını sağlamakla kalmaz. Ben şahsen Microsoft.VisualBasic kullanarak tek örnekli bir WPF uygulaması oluşturmak için doğru yolu nedir? (Dale Ragan cevap) ... Yeni uygulama başlangıcında alınan argümanları ilk tek örnekli uygulamaya geçirmenin daha kolay olduğunu gördüm.

Ama bu iş parçacığında önceki bazı kod ile ilgili olarak, ben üzerinde bir kilit istiyorum her seferinde bir Mutex oluşturmak tercih etmem. Tek örnekli bir uygulama için iyi olabilir, ancak diğer kullanımlarda bana aşırı yüklenilmiş gibi görünüyor.

Bu yüzden bunun yerine bu uygulamayı öneririm:

Kullanımı:

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

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

Mutex Global Sarıcı:

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

        // ************************************************************************

    }
}

Garson

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

Bir AbandonedMutexException özelliğine neden olabileceği için WaitOne içermeyen bir çözüm (WPF için). Bu çözüm, muteksin önceden oluşturulup oluşturulmadığını kontrol etmek için createdNew boolean öğesini döndüren Mutex yapıcısını kullanır. Ayrıca GetType (). GUID kullanır, böylece yürütülebilir dosyayı yeniden adlandırmak birden fazla örneğe izin vermez.

Küresel ve yerel muteks karşılaştırması için nota bakın: 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'ı uyguladığından, otomatik olarak serbest bırakılır, ancak tamlık çağrısı için atın:

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

Her şeyi bir temel sınıfa taşıyın ve kabul edilen yanıttan allowEveryoneRule öğesini ekleyin. Ayrıca, OS tarafından otomatik olarak serbest bırakıldığı için gerçekten gerekli görünmese de ReleaseMutex'i ekledi (uygulama çökerse ve asla ReleaseMutex'i çağırmazsa yeniden başlatmanız gerekir mi?).

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();
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.