¿Es seguro usar un Mutex para evitar que se ejecuten varias instancias del mismo programa?

StackOverflow https://stackoverflow.com/questions/646480

Pregunta

Estoy usando este código para evitar que una segunda instancia de mi programa se ejecute 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 aún se mantendrá. ¿Es eso cierto?

¿Fue útil?

Solución

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

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

Sin embargo, desafortunadamente, incluso con un bloque finalmente debes lidiar con el potencial de que un proceso finalice 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 un segundo proceso para obtener una AbandonedMutexException en la llamada WaitOne . Necesitarás una estrategia de recuperación para esto.

Le animo a leer en los detalles de Mutex clase . Usarlo no siempre es simple.


Ampliando la posibilidad de la condición de carrera:

Se puede producir la siguiente secuencia de eventos que provocaría una segunda instancia de la aplicación:

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

Otros consejos

Es más habitual y conveniente utilizar eventos de Windows para este propósito. Por ejemplo,

static EventWaitHandle s_event ;

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

Cuando su proceso finaliza o finaliza, 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, simplemente agregue un nombre de usuario registrado debidamente desglosado al nombre del evento.

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

Porque "evitando múltiples instancias" No está claramente definido. Puede significar

  1. Evitar que se inicien varias instancias en la misma sesión de usuario, sin importar cuántos escritorios tenga esa sesión de usuario, pero permitiendo que varias instancias se ejecuten simultáneamente para diferentes sesiones de usuario.
  2. Evitar que se inicien varias instancias en el mismo escritorio, pero permitir que se ejecuten varias instancias siempre que cada una esté en un escritorio separado.
  3. Evitar que se inicien varias instancias para la misma cuenta de usuario, sin importar cuántos escritorios o sesiones se ejecuten bajo esta cuenta, pero permitiendo que se ejecuten varias instancias simultáneamente para sesiones que se ejecutan bajo una cuenta de usuario diferente.
  4. Evitar múltiples instancias iniciadas en la misma máquina. Esto significa que no importa cuántos escritorios use un número arbitrario de usuarios, puede haber como máximo una instancia del programa 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 alguna aplicación ya no lo retiene (y las aplicaciones se finalizan si inicialmente no pueden crear el Mutext). Esto puede o no funcionar de manera idéntica en 'AppDomain-procesos' (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 programas maliciosos que pueden sentarse en un mutex. No es una preocupación aquí. Además, mutex sin prefijo creado en " Local " espacio. 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, finalizar un proceso tiene los siguientes resultados:

  • Todos los hilos restantes en el proceso están marcados para su terminació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.
  • Se establece el código de salida del proceso.
  • El objeto de proceso se señala.

Los objetos Mutex son objetos del núcleo, por lo que cualquier contenido de un proceso se cierra cuando el proceso finaliza (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 que usted y evitar que su aplicación se inicie.

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

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;

    /// <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 = <*>quot;Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = <*>quot;Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = <*>quot;Global\\{applicationName}";

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

    public void Dispose()
    {
        _event.Dispose();
    }
}

Si desea usar un enfoque basado en mutex, realmente debería usar un mutex local para restringe el enfoque solo a la sesión de inicio de sesión del usuario actual . 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 PInvoke to FindWindow seguido de SetForegroundWindow en primera instancia. Otra alternativa es verificar el proceso por nombre:

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

Ambas alternativas tienen una hipotética condición de carrera en la que dos instancias de la aplicación pueden 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 hacerlo realidad.

Otro problema con estas dos últimas alternativas es que no funcionarán cuando usen Terminal Services.

Use la aplicación con tiempo de espera y 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 úsalo:

    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 del programa: 1. Obtenga un System.Diagnostics.Process de SU aplicación usando Process.GetCurrentProcess () 2. Revise la colección de procesos abiertos con el nombre actual de su aplicación usando Process.GetProcessesByName (thisProcess.ProcessName) 3. Verifique cada proceso. Id contra este proceso. Id. Y si una instancia ya está abierta, al menos 1 coincidirá con el nombre pero no con el Id. De lo contrario, continúe abriendo la instancia

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émosle que lo estaba. Para hacer esto, uso User32.dll para transmitir un mensaje en el bucle de mensajería 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 .Show () o cualquier otra cosa

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top