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-ce sûr?

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 sera toujours conservé. Est-ce vrai?

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 se terminer brusquement et le laisser dans un état signalé, comme une exception. Cela ferait en sorte que les futures instances de processus ne pourraient pas démarrer.

Malheureusement, même avec un bloc finally , vous devez gérer le risque qu’un processus se termine sans libérer le mutex. Cela peut se produire par exemple si un utilisateur tue le processus via TaskManager. Il y a une condition de WaitOne dans votre code qui permettrait à un deuxième processus d’obtenir une WaitOne 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 du cours Mutex . L’utiliser n’est pas toujours simple.


Élargissement de la possibilité de condition de course:

La séquence d’événements suivante peut se produire et provoquer une seconde instance de l’application à lancer:

  1. Démarrage normal du processus.
  2. Le deuxième processus démarre et acquiert une poignée sur le mutex, mais il est désactivé avant l’appel WaitOne .
  3. Le processus n ° 1 est brusquement interrompu. Le mutex n’est pas détruit car le processus n ° 2 a un handle. Il est plutôt mis à un état abandonné.
  4. Le deuxième processus recommence à fonctionner et obtient une AbanonedMutexException .

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 si aucun handle ouvert ne rest.

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

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

Parce que “éviter les instances multiples” n’est pas clairement défini. Cela peut signifier

  1. Éviter le démarrage de plusieurs instances dans la même session utilisateur, quel que soit le nombre de postes de travail de cette session utilisateur, mais permettant à plusieurs instances de s’exécuter simultanément pour différentes sessions utilisateur.
  2. Éviter le lancement de plusieurs instances sur le même bureau, mais autoriser l’exécution de plusieurs instances tant que chacune se trouve sur un poste de travail distinct.
  3. Éviter le démarrage de plusieurs instances pour le même compte d’utilisateur, quel que soit le nombre de postes de travail ou de sessions exécutés sous ce compte, mais autoriser l’exécution simultanée de plusieurs instances pour des sessions exécutées sous un compte d’utilisateur différent.
  4. Eviter plusieurs instances démarrées sur le même ordinateur. Cela signifie que peu importe combien de bureaux sont utilisés par un nombre arbitraire d’utilisateurs, il peut y avoir au plus une instance du programme en cours d’exécution.

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

J’utilise cette méthode, je crois que c’est sûr parce que le Mutex est détruit s’il n’est plus tenu par une application (et les applications sont terminées si elles ne peuvent pas créer le Mutext). Cela peut ou peut ne pas fonctionner de manière identique dans ‘AppDomain-processes’ (voir le 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 notes dans d’autres réponses / commentaires sur les programmes malveillants pouvant s’asseoir sur un mutex. Pas une préoccupation ici. Aussi, mutex non préfixé créé dans l’espace “Local”. C’est probablement la bonne chose ici.

Voir: http://ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx – vient avec Jon Skeet 😉

Sous Windows, la fin d’un processus a les résultats suivants:

  • Tous les threads restants dans le processus sont marqués pour la résiliation.
  • Toutes les ressources allouées par le processus sont libérées.
  • Tous les objects du kernel sont fermés.
  • Le code de processus est supprimé de la mémoire.
  • Le code de sortie du processus est défini.
  • L’object du processus est signalé.

Les objects Mutex sont des objects du kernel, de sorte que tous les éléments détenus par un processus sont fermés lorsque le processus se termine (de toute façon dans Windows).

Mais, notez le bit suivant des documents CreateMutex ():

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

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

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

Voici l’extrait de code

 public enum ApplicationSingleInstanceMode { CurrentUserSession, AllSessionsOfCurrentUser, Pc } public class ApplicationSingleInstancePerUser: IDisposable { private readonly EventWaitHandle _event; ///  /// Shows if the current instance of ghost is the first ///  public bool FirstInstance { get; private set; } ///  /// Initializes ///  /// The application name /// The single mode public ApplicationSingleInstancePerUser(ssortingng applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession) { ssortingng 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(); } } 

Si vous souhaitez utiliser une approche basée sur 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 concernant l’élimination de ressources avec l’approche mutex.

Une mise en garde est qu’une approche basée sur 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 à PInvoke to FindWindow suivi de SetForegroundWindow sur la première instance. Une autre solution consiste à vérifier votre processus par nom:

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

Ces deux dernières alternatives ont une condition de course hypothétique où deux instances de l’application peuvent être démarrées simultanément et se détecter ensuite. Il est peu probable que cela se produise en pratique – en fait, pendant les 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.

Utilisez l’application avec les parameters de délai d’expiration et de sécurité en évitant les exceptions AbandonedMutexException. J’ai utilisé mon cours personnalisé:

 private class SingleAppMutexControl : IDisposable { private readonly Mutex _mutex; private readonly bool _hasHandle; public SingleAppMutexControl(ssortingng 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(ssortingng[] args) { try { const ssortingng 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"); } } 

Voici comment je l’ai abordé

Dans la classe Program: 1. Procurez-vous un System.Diagnostics.Process de VOTRE application à l’aide de Process.GetCurrentProcess () 2. Parcourez la collection de processus ouverts avec le nom actuel de votre application à l’aide de Process.GetProcessesByName (thisProcess.ProcessName) 3. Vérifiez chaque processus.Id contre thisProcess.Id et si une instance est déjà ouverte, au moins 1 correspondra au nom mais pas à l’identifiant, sinon continuez l’instance d’ouverture

 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 terminer ceci serait de présenter l’instance déjà ouverte à l’utilisateur, il y a des chances qu’ils ne sachent pas que c’était ouvert, alors montrons-le. 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 ce message parvient à l’utilisateur, Form .Show () ou autres