Est-ce que l'utilisation d'un Mutex pour empêcher plusieurs instances du même programme de fonctionner sans danger?
j'utilise ce code pour empêcher une deuxième instance de mon programme de fonctionner 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.");
}
j'ai peur que si quelque chose fait une exception et que l'application tombe en panne, le Mutex sera toujours tenu. Est-ce vrai?
10 réponses
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 brusquement se terminer et le laisser dans un état signalé, comme une exception. Cela ferait en sorte que les instances de processus futures ne seraient pas en mesure de démarrer.
malheureusement, même avec un bloc finally
vous devez faire face au potentiel qu'un processus sera résilié sans libérer le mutex. Cela peut se produire par exemple si un utilisateur tue le processus par TaskManager. Il y a une condition de race dans votre code qui permettrait à un second processus d'obtenir un AbandonedMutexException
dans l'appel WaitOne
. Vous aurez besoin d'un programme de rétablissement pour cette.
je vous encourage à lire sur les détails de la classe Mutex . L'aide il n'est pas toujours simple.
Développer la possibilité de la condition de course:
la séquence suivante d'événements peut se produire qui causerait une deuxième instance de l'application de jeter:
- procédure Normale de démarrage.
- Deuxième processus démarre et acquiert une poignée pour le mutex, mais est mis à l'écart avant la
WaitOne
appel. - le processus n ° 1 est brusquement terminé. Le mutex n'est pas détruit parce que le processus 2 a une poignée. Il est au contraire placé dans un État abandonné.
- le second processus recommence à fonctionner et obtient un
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 sort ou se termine, Windows fermera l'événement pour vous, et le détruira s'il ne reste plus de poignées ouvertes.
ajouté : pour gérer les sessions, utilisez les préfixes Local\
et Global\
pour le nom de l'événement (ou mutex). Si votre application est par utilisateur, il suffit d'ajouter un nom d'utilisateur dûment modifié et connecté à le 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. Il peut signifier
- éviter les instances multiples lancées dans la même session de l'utilisateur, peu importe le nombre d'ordinateurs de bureau que la session de l'utilisateur A, mais permettant à plusieurs instances de s'exécuter simultanément pour différentes sessions de l'utilisateur.
- éviter les instances multiples desktop, mais permettant à plusieurs instances de fonctionner aussi longtemps que chacune est dans un bureau séparé.
- éviter les instances multiples lancées pour le même compte d'utilisateur, peu importe le nombre d'ordinateurs de bureau ou de sessions tournant sous ce compte, mais permettant aux instances multiples de tourner simultanément pour des sessions tournant sous un compte d'utilisateur différent.
- éviter les instances multiples lancées sur la même machine. Cela signifie que peu importe combien d'ordinateurs de bureau sont utilisés par un nombre arbitraire d'utilisateurs, il peut y avoir au plus une occurrence de programme en cours d'exécution.
en utilisant un mutex, vous utilisez essentiellement le numéro de définition 4.
j'utilise cette méthode, je crois qu'il est sûr parce que le Mutex est détruit si pas longtemps détenu par une application (et les applications sont terminées si si elles ne peuvent pas créer initialement le Mutext). Cela peut ou non fonctionner de manière identique dans les "AppDomain process" (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. En outre, mutex non figé créé dans Un espace "Local". C'est probablement le-droit-chose ici.
voir: http://ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx -- vient avec Jon Skeet ;-)
sur Windows, la fin d'un processus a les résultats suivants:
- tous les fils restants dans le processus sont marqués pour la 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 retiré de la mémoire.
- le code de sortie du processus est défini.
- l'objet process est indiqué.
Mutex les objets sont les objets de noyau, de sorte que tout détenu par un processus sont fermés lorsque le processus se termine (sous Windows en tout cas).
mais, notez le bit suivant de CreateMutex () docs:
si vous utilisez un mutex nommé pour limiter votre application à une seule instance, un utilisateur malveillant peut créer ce mutex avant vous et empêcher votre application de démarrer.
Oui, c'est sûr, je suggère le modèle suivant, parce que vous devez vous assurer que le Mutex
est toujours libéré.
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;
/// <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();
}
}
si vous voulez utiliser une approche basée sur mutex, vous devriez vraiment utiliser un mutex local à restreindre l'approche à la seule session de connexion de l'utilisateur courant . Notons également l'autre mise en garde importante à propos du lien entre l'élimination de ressources robustes et 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 essaie de lancer une deuxième instance.
Une alternative est de PInvoke à FindWindow suivi de SetForegroundWindow en première instance. Une autre alternative est de vérifier votre processus par son 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 pourraient être lancées simultanément et se détecter l'une l'autre. Il est peu probable que cela se produise dans la pratique - en fait, pendant les tests, je n'ai pas pu le faire.
un autre problème avec ces deux dernières solutions sont qu'elles ne fonctionneront pas lorsqu'on utilise les services de Terminal.
Utilisez app avec timeout et les paramètres 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");
}
}
, Voici comment j'ai abordé cette
dans la classe de programme: 1. Obtenir un Système.Diagnostic.Traitement de votre demande à L'aide du processus.Fonction getcurrentprocess() 2. Étape à travers la collection de processus ouverts avec le nom actuel de votre demande en utilisant le processus.GetProcessesByName (ceprocess.ProcessName) 3. Vérifiez chaque processus.Id against this process.Id et si une instance est déjà ouverte alors au moins 1 correspondra nom mais pas Id, sinon continuer l'ouverture 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 belle touche pour finir cela serait de présenter l'instance déjà ouverte à l'utilisateur, il y a des chances qu'ils ne savaient pas qu'elle était ouverte, alors montrons-leur que c'était le cas. Pour ce faire j'utilise User32.dll pour diffuser un message dans la boucle de messagerie Windows, un message personnalisé, et j'ai mon application l'écouter dans la méthode WndProc, et s'il obtient ce message, il se présente à l'utilisateur, forme.Show() ou autres joyeusetés