406 votes

Quel est le bon modèle pour utiliser un Global Mutex en C# ?

La classe Mutex est très mal comprise, et les mutex globaux encore plus.

Quel est le bon modèle, sûr, à utiliser lors de la création de mutex globaux ?

Un qui fonctionne

  • Quelle que soit la langue dans laquelle se trouve ma machine
  • est garanti pour libérer le mutex correctement
  • Optionnellement, il n'est pas nécessaire d'attendre éternellement si le mutex n'est pas acquis.
  • Traite les cas où d'autres processus abandonnent le mutex

444voto

Sam Saffron Points 56236

Je veux m'assurer que ça existe, parce que c'est si difficile à faire correctement :

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 votes

Vous pouvez omettre using pour vérifier createdNew et ajouter mutex.Dispose() à l'intérieur de finally . Je ne peux pas l'expliquer clairement (je ne sais pas pourquoi) pour le moment, mais je me suis retrouvé dans une situation où mutex.WaitOne a retourné true après createdNew est devenu false (J'ai acquis le mutex dans l'actuelle AppDomain et a ensuite chargé un nouveau AppDomain et exécuté le même code à partir de celui-ci).

0 votes

1. Est-ce que exitContext = false faire quoi que ce soit dans mutex.WaitOne(5000, false) ? Il semble que cela ne puisse provoquer qu'une assertion dans CoreCLR. 2. Si quelqu'un se demande, en Mutex la raison pour laquelle initiallyOwned est false s'explique en partie par cet article de MSDN .

5 votes

Un conseil : attention à l'utilisation de Mutex avec ASP.NET : "La classe Mutex impose l'identité des threads, de sorte qu'un mutex ne peut être libéré que par le thread qui l'a acquis. En revanche, la classe Semaphore n'impose pas l'identité des threads". Une requête ASP.NET peut être traitée par plusieurs threads.

144voto

deepee1 Points 4449

À l'aide de la réponse acceptée, je crée une classe d'aide pour que vous puissiez l'utiliser de la même manière que vous utiliseriez l'instruction Lock. J'ai juste pensé que je devais partager.

Utilisez :

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

Et la classe d'aide :

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

0 votes

Superbe travail, merci ! Pour info : j'ai mis à jour la méthode Dispose ci-dessus pour éviter l'avertissement CA2213 pendant l'analyse de code. Le reste est passé sans problème. Pour plus de détails, consultez msdn.microsoft.com/query/

1 votes

Sémantiquement, le (new SingleGlobalInstance(xxx)) laisse croire que chaque mutex est distinct alors qu'en réalité, ils font tous référence au même. Ne serait-il pas plus clair de créer un "using (new MutexLocker(Mutex mutexOpt = null)", où l'on créerait/définirait par défaut un mutex statique qui serait créé exactement une fois par application (peut-être caché dans la classe comme vous l'avez fait) ? De plus, le terme "global" implique généralement "l'application dans son ensemble", alors que le terme "système" implique "le serveur dans son ensemble", ce qui correspond, je crois, à un mutex nommé. msdn.microsoft.com/fr/us/library/hw29w7t1.aspx

3 votes

Comment puis-je gérer l'exception de dépassement de délai dans la classe qui consomme SingleGlobalInstance. De plus, est-ce une bonne pratique de lancer une exception lors de la construction d'une instance ?

16voto

Van Nguyen Points 11

Il y a une condition de course dans la réponse acceptée lorsque 2 processus fonctionnant sous 2 utilisateurs différents essaient d'initialiser le mutex en même temps. Après que le premier processus ait initialisé le mutex, si le second processus essaie d'initialiser le mutex avant que le premier processus ne fixe la règle d'accès à tout le monde, une exception non autorisée sera levée par le second processus.

Voir ci-dessous la réponse corrigée :

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

9 votes

Notez que ce problème est maintenant corrigé dans la réponse acceptée.

10voto

Liam Points 5214

Cet exemple se terminera après 5 secondes si une autre instance est déjà en cours d'exécution.

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

10voto

sol Points 51

Ni Mutex ni WinApi CreateMutex() ne fonctionnent pour moi.

Une solution alternative :

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

        Application.Run(new MainForm());

        SingleApplicationDetector.Close();
    }
}

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

Raison d'utiliser Sémaphore au lieu de Mutex :

La classe Mutex applique l'identité des threads, de sorte qu'un mutex ne peut être libéré que par le thread qui l'a acquis. En revanche, la classe Sémaphore n'impose pas l'identité des threads.

<< System.Threading.Mutex

Réf : Sémaphore.OpenExisting()

9 votes

Conditions de course possibles entre Semaphore.OpenExisting et new Semaphore .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X