web-dev-qa-db-fra.com

L'utilisation d'un mutex pour empêcher plusieurs instances du même programme de s'exécuter en toute sécurité?

J'utilise ce code pour empêcher une deuxième instance de mon programme de s'exécuter en même temps, est-il sécurisé?

Mutex appSingleton = new System.Threading.Mutex(false, "MyAppSingleInstnceMutx");
if (appSingleton.WaitOne(0, false)) {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
    appSingleton.Close();
} else {
    MessageBox.Show("Sorry, only one instance of MyApp is allowed.");
}

Je crains que si quelque chose lève une exception et que l'application se bloque, le Mutex aura toujours lieu. Est-ce vrai?

43
Malfist

En général oui cela fonctionnera. Cependant, le diable est dans les détails.

Tout d'abord, vous voulez fermer le mutex dans un bloc finally. Sinon, votre processus pourrait s’arrêter brusquement et le laisser dans un état signalé, à la manière d’une exception. Cela ferait en sorte que les instances de processus futures ne puissent pas démarrer. 

Malheureusement, même avec un bloc finally, vous devez gérer le risque de mettre fin à un processus sans libérer le mutex. Cela peut se produire par exemple si un utilisateur tue le processus via le Gestionnaire des tâches. Votre code contient une condition de concurrence qui autoriserait un deuxième processus à obtenir un AbandonedMutexException dans l'appel WaitOne. Vous aurez besoin d'une stratégie de récupération pour cela. 

Je vous encourage à lire les détails de la classe Mutex . L'utiliser n'est pas toujours simple.


Élargir la possibilité de condition de concurrence:

La séquence d'événements suivante peut se produire, ce qui provoquerait le lancement d'une deuxième instance de l'application:

  1. Démarrage normal du processus.
  2. Le second processus démarre et acquiert un identifiant pour le mutex mais est déconnecté avant l'appel WaitOne.
  3. Le processus n ° 1 est brusquement arrêté. Le mutex n'est pas détruit car le processus n ° 2 a un handle. Au lieu de cela, il est défini sur un état abandonné.
  4. Le second processus recommence à courir et obtient une AbanonedMutexException.
39
JaredPar

Il est plus habituel et pratique d’utiliser les événements Windows à cette fin. Par exemple.

static EventWaitHandle s_event ;

bool created ;
s_event = new EventWaitHandle (false, 
    EventResetMode.ManualReset, "my program#startup", out created) ;
if (created) Launch () ;
else         Exit   () ;

Lorsque votre processus se termine ou se termine, Windows ferme l'événement pour vous et le détruit s'il ne reste plus de descripteurs ouverts.

Ajouté : pour gérer les sessions, utilisez les préfixes Local\ et Global\ pour le nom de l'événement (ou du mutex). Si votre application est utilisateur par utilisateur, ajoutez simplement un nom d'utilisateur connecté mutilé de manière appropriée au nom de l'événement.

56
Anton Tykhyy

Vous pouvez utiliser un mutex, mais assurez-vous d’abord que c’est vraiment ce que vous voulez.

Parce que "éviter plusieurs instances" n'est pas clairement défini. Cela peut signifier

  1. Éviter plusieurs instances démarrées dans la même session utilisateur, quel que soit le nombre de bureaux de cette session utilisateur, tout en permettant à plusieurs instances de s'exécuter simultanément pour différentes sessions utilisateur.
  2. Éviter plusieurs instances démarrées sur le même bureau, mais permettre à plusieurs instances de s'exécuter tant qu'elles se trouvent dans un bureau séparé. 
  3. Éviter plusieurs instances démarrées pour le même compte d'utilisateur, quel que soit le nombre de bureaux ou de sessions exécutés sous ce compte, mais en permettant à plusieurs instances de s'exécuter simultanément pour des sessions exécutées sous un autre compte d'utilisateur.
  4. Éviter plusieurs instances démarrées sur le même ordinateur. Cela signifie que, quel que soit le nombre de bureaux utilisés par un nombre arbitraire d'utilisateurs, il ne peut y avoir au plus qu'une instance du programme en cours d'exécution.

En utilisant un mutex, vous utilisez essentiellement le nombre défini 4.

9
Stefan

J'utilise cette méthode, je pense qu'elle est sûre car le Mutex est détruit s'il n'est plus détenu par une application (et les applications sont fermées si elles ne peuvent pas créer le Mutext initialement). Cela peut ou non fonctionner de manière identique dans 'AppDomain-process' (voir lien en bas):

// Make sure that appMutex has the lifetime of the code to guard --
// you must keep it from being collected (and the finalizer called, which
// will release the mutex, which is not good here!).
// You can also poke the mutex later.
Mutex appMutex;

// In some startup/initialization code
bool createdNew;
appMutex = new Mutex(true, "mutexname", out createdNew);
if (!createdNew) {
  // The mutex already existed - exit application.
  // Windows will release the resources for the process and the
  // mutex will go away when no process has it open.
  // Processes are much more cleaned-up after than threads :)
} else {
  // win \o/
}

Ce qui précède souffre de remarques dans d’autres réponses/commentaires selon lesquels des programmes malveillants peuvent s’asseoir sur un mutex. Pas un problème ici. En outre, mutex non préfixé créé dans un espace "local". C'est probablement la bonne chose ici.

Voir: http://ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx - est livré avec Jon Skeet ;-)

9
user166390

Sous Windows, l’arrêt d’un processus entraîne les résultats suivants:

  • Tous les threads restants dans le processus sont marqués pour fin.
  • Toutes les ressources allouées par le processus sont libérées.
  • Tous les objets du noyau sont fermés.
  • Le code de processus est supprimé de la mémoire.
  • Le code de sortie du processus est défini.
  • L'objet de processus est signalé.

Les objets Mutex sont des objets du noyau, donc ceux qui sont détenus par un processus sont fermés lorsque le processus se termine (de toute façon sous Windows).

Cependant, notez le bit suivant dans la documentation CreateMutex ():

Si vous utilisez un mutex nommé pour limiter votre application à une seule instance, un utilisateur malveillant peut le créer avant vous et empêcher ainsi le démarrage de votre application.

3
Michael Burr

Oui, c'est sûr, je suggère le schéma suivant, car vous devez vous assurer que la variable Mutex est toujours publiée.

using( Mutex mutex = new Mutex( false, "mutex name" ) )
{
    if( !mutex.WaitOne( 0, true ) )
    {
        MessageBox.Show("Unable to run multiple instances of this program.",
                        "Error",  
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);
    }
    else
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());                  
    }
}
2
arul

Voici l'extrait de code

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}
2
Siarhei Kuchuk

Utilisez l'application avec des paramètres de délai d'expiration et de sécurité en évitant AbandonedMutexException. J'ai utilisé ma classe personnalisée:

private class SingleAppMutexControl : IDisposable
    {
        private readonly Mutex _mutex;
        private readonly bool _hasHandle;

        public SingleAppMutexControl(string appGuid, int waitmillisecondsTimeout = 5000)
        {
            bool createdNew;
            var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                MutexRights.FullControl, AccessControlType.Allow);
            var securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);
            _mutex = new Mutex(false, "Global\\" + appGuid, out createdNew, securitySettings);
            _hasHandle = false;
            try
            {
                _hasHandle = _mutex.WaitOne(waitmillisecondsTimeout, false);
                if (_hasHandle == false)
                    throw new System.TimeoutException();
            }
            catch (AbandonedMutexException)
            {
                _hasHandle = true;
            }
        }

        public void Dispose()
        {
            if (_mutex != null)
            {
                if (_hasHandle)
                    _mutex.ReleaseMutex();
                _mutex.Dispose();
            }
        }
    }

et l'utiliser:

    private static void Main(string[] args)
    {
        try
        {
            const string appguid = "{xxxxxxxx-xxxxxxxx}";
            using (new SingleAppMutexControl(appguid))
            {
                //run main app
                Console.ReadLine();
            }
        }
        catch (System.TimeoutException)
        {
            Log.Warn("Application already runned");
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Fatal Error on running");
        }
    }
0
vivlav

Si vous souhaitez utiliser une approche basée sur le mutex, vous devez utiliser un mutex local pour restreindre l'approche à la seule session de connexion de l'utilisateur actuel . Et notez également l’autre mise en garde importante dans ce lien sur l’élimination robuste des ressources avec l’approche mutex.

Un inconvénient est qu'une approche basée sur le mutex ne vous permet pas d'activer la première instance de l'application lorsqu'un utilisateur tente de démarrer une seconde instance. 

Une alternative est de PInvoke à FindWindow suivi de SetForegroundWindow sur la première instance. Une autre alternative consiste à vérifier votre processus par son nom:

Process[] processes = Process.GetProcessesByName("MyApp");
if (processes.Length != 1)
{
    return;
} 

Ces deux dernières solutions ont une condition de concurrence hypothétique dans laquelle deux instances de l'application peuvent être démarrées simultanément, puis se détecter l'une l'autre. Il est peu probable que cela se produise dans la pratique - en fait, lors des tests, je ne pouvais pas y arriver. 

Un autre problème avec ces deux dernières alternatives est qu'elles ne fonctionneront pas avec les services Terminal Server.

0
RoadWarrior

Voici comment j'ai abordé cette question

Dans la classe programme: 1. Obtenez un System.Diagnostics.Process de VOTRE application à l'aide de Process.GetCurrentProcess () 2. Parcourez la collection de processus ouverts portant le nom actuel de votre application à l'aide de Process.GetProcessesByName (thisProcess.ProcessName) 3. Vérifiez chaque processus.Id par rapport à thisProcess.Id et si une instance est déjà ouverte, au moins un correspondra à name mais pas à Id, sinon continuez l'instance

using System.Diagnostics;

.....    

static void Main()
{
   Process thisProcess = Process.GetCurrentProcess();
   foreach(Process p in Process.GetProcessesByName(thisProcess.ProcessName))
   {
      if(p.Id != thisProcess.Id)
      {
         // Do whatever u want here to alert user to multiple instance
         return;
      }
   }
   // Continue on with opening application

Une bonne idée pour finir serait de présenter l'instance déjà ouverte à l'utilisateur. Il est probable qu'ils ne sachent pas qu'elle était ouverte, alors montrons-leur qu'elle l'était. Pour ce faire, j'utilise User32.dll pour diffuser un message dans la boucle de messagerie Windows, un message personnalisé, et mon application l'écoute dans la méthode WndProc. Si elle reçoit ce message, elle se présente à l'utilisateur, Form .Show () ou tout le reste

0
Wanabrutbeer