¿Está utilizando un Mutex para evitar que varias instancias del mismo progtwig se ejecuten de forma segura?

Estoy usando este código para evitar que se ejecute una segunda instancia de mi progtwig al mismo tiempo, ¿es seguro?

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."); } 

Me preocupa que si algo arroja una excepción y la aplicación falla, el Mutex seguirá retenido. ¿Es eso cierto?

En general, sí, esto funcionará. Sin embargo, el diablo está en los detalles.

En primer lugar, desea cerrar el mutex en un bloque finally . De lo contrario, su proceso podría terminar abruptamente y dejarlo en un estado señalizado, como una excepción. Eso haría que las instancias de procesos futuros no puedan iniciarse.

Desafortunadamente, incluso con un locking final, debes lidiar con el potencial de que un proceso se termine sin liberar el mutex. Esto puede suceder, por ejemplo, si un usuario mata el proceso a través de TaskManager. Hay una condición de carrera en su código que permitiría que un segundo proceso obtenga una AbandonedMutexException en la llamada de WaitOne . Necesitarás una estrategia de recuperación para esto.

Te animo a que leas los detalles de la clase Mutex . Usarlo no siempre es simple.


Ampliando la posibilidad de condición de carrera:

La siguiente secuencia de eventos puede ocurrir y causar una segunda instancia de la aplicación:

  1. Inicio de proceso normal.
  2. El segundo proceso se inicia y adquiere un control para el mutex, pero se desconecta antes de la llamada a WaitOne .
  3. El proceso n. ° 1 termina abruptamente. El mutex no se destruye porque el proceso n. ° 2 tiene un asa. En su lugar, se establece en un estado abandonado.
  4. El segundo proceso comienza a ejecutarse nuevamente y obtiene una AbanonedMutexException .

Es más habitual y conveniente usar eventos de Windows para este propósito. P.ej

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

Cuando el proceso finalice o finalice, Windows cerrará el evento por usted y lo destruirá si no quedan identificadores abiertos.

Agregado : para administrar sesiones, use los prefijos Local\ y Global\ para el nombre del evento (o mutex). Si su aplicación es por usuario, solo agregue el nombre de un usuario conectado correctamente destrozado al nombre del evento.

Puede usar un mutex, pero primero asegúrese de que esto sea realmente lo que desea.

Porque “evitar instancias múltiples” no está claramente definido. Puede significar

  1. Evitar varias instancias comenzó en la misma sesión de usuario, sin importar la cantidad de escritorios que tenga esa sesión de usuario, pero permitiendo que varias instancias se ejecuten simultáneamente para diferentes sesiones de usuario.
  2. Evitar varias instancias comenzó en el mismo escritorio, pero permite que se ejecuten varias instancias siempre que cada una se encuentre en un escritorio diferente.
  3. Evita que se inicien varias instancias para la misma cuenta de usuario, independientemente de la cantidad de escritorios o sesiones que se ejecutan bajo esta cuenta, pero permitiendo que varias instancias se ejecuten simultáneamente para las sesiones que se ejecutan en una cuenta de usuario diferente.
  4. Evitar varias instancias comenzó en la misma máquina. Esto significa que no importa cuántos escritorios use una cantidad arbitraria de usuarios, puede haber como máximo una instancia del progtwig ejecutándose.

Al usar un mutex, básicamente estás usando el número de definición 4.

Utilizo este método, creo que es seguro porque el Mutex se destruye si no lo tiene ninguna aplicación (y las aplicaciones se terminan si no pueden crear inicialmente el Mutext). Esto puede o no funcionar de manera idéntica en ‘AppDomain-processes’ (ver enlace en la parte inferior):

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

Lo anterior sufre de notas en otras respuestas / comentarios sobre progtwigs maliciosos que pueden sentarse en un mutex. No es una preocupación aquí. Además, mutex no prefijado creado en el espacio “Local”. Probablemente sea lo correcto aquí.

Ver: http://ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx – viene con Jon Skeet 😉

En Windows, la finalización de un proceso tiene los siguientes resultados:

  • Cualquier subproceso restante en el proceso está marcado para la finalización.
  • Todos los recursos asignados por el proceso se liberan.
  • Todos los objetos del núcleo están cerrados.
  • El código de proceso se elimina de la memoria.
  • El código de salida del proceso está establecido.
  • El objeto de proceso es señalado.

Los objetos Mutex son objetos kernel, por lo que cualquier contenido de un proceso se cierra cuando finaliza el proceso (en Windows de todos modos).

Pero, tenga en cuenta el siguiente bit de los documentos CreateMutex ():

Si está utilizando un mutex con nombre para limitar su aplicación a una sola instancia, un usuario malintencionado puede crear este mutex antes de hacerlo e impedir que se inicie su aplicación.

Sí, es seguro, sugeriría el siguiente patrón, porque necesita asegurarse de que siempre se libere el Mutex .

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

Aquí está el fragmento de código

 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(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 desea utilizar un enfoque basado en mutex, realmente debería usar un mutex local para restringir el enfoque solo a la sesión de inicio de sesión del usuario curado . Y también tenga en cuenta la otra advertencia importante en ese enlace sobre la eliminación robusta de recursos con el enfoque mutex.

Una advertencia es que un enfoque basado en mutex no le permite activar la primera instancia de la aplicación cuando un usuario intenta iniciar una segunda instancia.

Una alternativa es invocar a FindWindow seguido de SetForegroundWindow en la primera instancia. Otra alternativa es verificar su proceso por su nombre:

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

Ambas alternativas tienen una condición de carrera hipotética en la que dos instancias de la aplicación podrían iniciarse simultáneamente y luego detectarse entre sí. Es poco probable que esto suceda en la práctica; de hecho, durante las pruebas no pude lograrlo.

Otro problema con estas dos últimas alternativas es que no funcionarán cuando se utilicen los Servicios de Terminal Server.

Utilice la aplicación con el tiempo de espera y la configuración de seguridad para evitar AbandonedMutexException. Usé mi clase personalizada:

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

y úsala:

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

Así es como me he acercado a esto

En la clase Program: 1. Obtenga un System.Diagnostics.Process de SU aplicación usando Process.GetCurrentProcess () 2. Pase por la colección de procesos abiertos con el nombre actual de su aplicación usando Process.GetProcessesByName (thisProcess.ProcessName) 3. Verifique cada proceso. Id frente a este Id. De proceso y, si ya se abrió una instancia, al menos 1 coincidirá con el nombre pero no con el Id; de lo contrario, continuará la instancia de apertura.

 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 

Un buen toque para terminar esto sería presentar la instancia ya abierta al usuario, lo más probable es que no supieran que estaba abierta, así que demostrémosles que sí. Para hacer esto, uso User32.dll para transmitir un mensaje en el bucle de mensajes de Windows, un mensaje personalizado, y hago que mi aplicación lo escuche en el método WndProc, y si recibe este mensaje, se presenta al usuario, Formulario .Mostrar () o lo que sea