2010-02-24 46 views
7

Si tiene dos hilos dentro de una aplicación, y no desea que se ejecuten una determinada pieza de código al mismo tiempo, sólo puede poner un candado alrededor de la pieza de código, como esto:C#: ¿Cómo evitar que dos instancias de una aplicación hagan lo mismo al mismo tiempo?

lock (someObject) { 
    // ... some code 
} 

Pero ¿cómo haces lo mismo en procesos separados? Pensé que esto es lo que se utiliza un "mutex global" para, así que probé la clase Mutex de varias maneras, pero no parece cumplir con mis necesidades, que son:

  • Si usted es el único instancia, adelante y ejecute el código.
  • Si es la segunda instancia, espere a que termine la primera, luego ejecute el código.
  • No arroje excepciones.

problemas que se encontraron con:

  • Sólo una instancia de un objeto Mutex en una cláusula using(){...} no parece hacer nada; las dos instancias todavía se ejecutan felizmente al mismo tiempo
  • Llamar a .WaitOne() en el Mutex hace que se ejecute la primera instancia y el segundo espere, pero el segundo espera indefinidamente, incluso después de las primeras llamadas .ReleaseMutex() y deja el alcance using(){}.
  • .WaitOne() arroja una excepción cuando el primer proceso sale (System.Threading.AbandonedMutexException).

¿Cómo soluciono esto? Las soluciones que no involucran Mutex son bienvenidas, especialmente porque Mutex parece ser específico de Windows.

+3

¿Está usando .NET, y se queja de que 'Mutex' es específico de Windows? –

+2

@Anon .: http://www.mono-project.com/Main_Page – Randolpho

+1

@Randolpho: cuyo objetivo es implementar toda la biblioteca .NET. Incluyendo 'System.Threading.Mutex'. Si es parte de la biblioteca .NET, puede esperar que se ejecute en cualquier plataforma .NET (incluido el mono, una vez que termine de implementar esa parte). –

Respuesta

5

que tienen dos aplicaciones:

ConsoleApplication1.cs

using System; 
using System.Threading; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Mutex mutex = new Mutex(false, "AwesomeMutex"); 

      Console.WriteLine("ConsoleApplication1 created mutex, waiting . . ."); 

      mutex.WaitOne(); 

      Console.Write("Waiting for input. . ."); 
      Console.ReadKey(true); 

      mutex.ReleaseMutex(); 
      Console.WriteLine("Disposed mutex"); 
     } 
    } 
} 

ConsoleApplication2.cs

using System; 
using System.Threading; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Mutex mutex = new Mutex(false, "AwesomeMutex"); 
      Console.WriteLine("ConsoleApplication2 Created mutex"); 

      mutex.WaitOne(); 

      Console.WriteLine("ConsoleApplication2 got signalled"); 

      mutex.ReleaseMutex(); 
     } 
    } 
} 

partir ConsoleApplication1, seguido de ConsoleAplication2 funciona perfectamente sin errores. Si su código aún se dispara, es un error con su código, no con la clase Mutex.

+0

Muchas gracias. Podría haber jurado que esto es exactamente lo que hice, pero debo haber hecho algo diferente porque no funcionó y ahora lo hace. Gracias de nuevo. – Timwi

4

He usado Mutex con éxito para este propósito y puedo confirmar que funciona, aunque hubo algunas peculiaridades.

Tengo un ejemplo de código de trabajo completo en casa. Solo publique un comentario a esta respuesta si desea que agregue el ejemplo del código esta noche.

ACTUALIZACIÓN:

Aquí está el código despojada de mi aplicación de producción. Esta es una aplicación de consola, pero el mismo principio debe aplicarse a cualquier tipo de aplicación. Ejecutar con un argumento de línea de comando de

--mutex 

para probar la lógica mutex.

Tenga en cuenta que, en mi caso, el mutex protege realmente la mayor parte del proceso, pero no hay ninguna razón para usarlo de esa manera. Eso es justo lo que se necesitaba en este caso.

using System; 
using System.IO; 
using System.Collections.Generic; 
using System.Text; 
using System.Diagnostics; 
using System.Threading; 

namespace MyNameSpace 
{ 
    class Program 
    { 
     // APP_GUID can be any unique string. I just opted for a Guid. This isn't my real one :-) 
     const string APP_GUID = "1F5D24FA-7032-4A94-DA9B-F2B6240F45AC"; 

     static int Main(string[] args) 
     { 
      bool testMutex = false; 
      if (args.Length > 0 && args[0].ToUpper() == "--MUTEX") 
      { 
       testMutex = true; 
      } 

      // Got variables, now only allow one to run at a time. 

      int pid = System.Diagnostics.Process.GetCurrentProcess().Id; 

      int rc = 0; 

      Mutex mutex = null; 
      bool obtainedMutex = false; 
      int attempts = 0; 
      int MAX_ATTEMPTS = 4; 

      try 
      { 
       mutex = new Mutex(false, "Global\\" + APP_GUID); 

       Console.WriteLine("PID " + pid + " request mutex."); 

       while (!obtainedMutex && attempts < MAX_ATTEMPTS) 
       { 
        try 
        { 
         if (!mutex.WaitOne(2000, false)) 
         { 
          Console.WriteLine("PID " + pid + " could not obtain mutex."); 
          // Wait up to 2 seconds to get the mutex 
         } 
         else 
         { 
          obtainedMutex = true; 
         } 
        } 
        catch (AbandonedMutexException) 
        { 
         Console.WriteLine("PID " + pid + " mutex abandoned!"); 
         mutex = new Mutex(false, "Global\\" + APP_GUID); // Try to re-create as owner 
        } 

        attempts++; 
       } 

       if (!obtainedMutex) 
       { 
        Console.WriteLine("PID " + pid + " gave up on mutex."); 
        return 102; 
       } 


       Console.WriteLine("PID " + pid + " got mutex."); 

       // This is just to test the mutex... keep one instance open until a key is pressed while 
       // other instances attempt to acquire the mutex 
       if (testMutex) 
       { 
        Console.Write("ENTER to exit mutex test...."); 
        Console.ReadKey(); 
        return 103; 
       } 

       // Do useful work here 

      } 
      finally 
      { 
       if (mutex != null && obtainedMutex) mutex.ReleaseMutex(); 
       mutex.Close(); 
       mutex = null; 
      } 

      return rc; 
     } 
    } 
} 
+0

@Timwi: publicaré el código esta noche. Me rasqué la cabeza un poco para que funcionara, pero ahora está siendo utilizado en una aplicación de producción de gran volumen sin ningún problema. –

+0

gracias compañero .. más útil. – baash05

+0

finalmente podría bloquearse ... si el mutex es nulo, cerrar no funcionaría ... No estoy seguro de cómo podría ser nulo, pero si no puede, entonces la prueba de nulo no sería necesaria. – baash05

4
+0

Sí, un Mutex con nombre es la forma en que los procesos pueden compartir esta primitiva de Windows. –

+0

Lo hice. No funcionó. Algún código de ejemplo funcional sería agradable. – Timwi

+1

@Timwi - ¿Podría proporcionar el código que está utilizando que no funciona? Es posible que haya algo más que esté haciendo y que esté haciendo que esto falle. – Nick

0

Aunque recomendaría usar Mutex e investigar si el problema es su código en sí, una alternativa muy compleja y muy frágil podría ser el uso de "archivos de bloqueo".

Básicamente, crea un archivo temporal en el sistema de archivos con un nombre que reconocen ambos procesos.Si el archivo existe, el bloqueo está en su lugar; el otro proceso debería liberar cualquier otro bloqueo, esperar e intentar volver a adquirirlo. Si el archivo no existe, el bloqueo no está en su lugar; el proceso debe crear el archivo para completar el bloqueo y continuar el proceso, eliminando el archivo al soltar el bloqueo.

Como dije, es muy complejo y frágil, pero no usa Mutex.

Como alternativa, use Mutex.

0

Si no quiere usar un Mutex, yo diría que hacer rodar el suyo usando un archivo que existe sería una solución simple. Si el archivo existe, no inicie uno nuevo. Sin embargo, tendrá que manejar los casos de excepción en los que un proceso finaliza temprano y el archivo no se limpia.

Volviendo a mutex por un momento, este es solo un 'objeto' del SO a falta de un mejor término. Realmente necesitas algo como esto en cada sistema operativo que uses. Estoy seguro de que los otros .net clr te permitirán acceder a esas primitivas del sistema también. Solo terminaría cada uno usando un ensamblaje definido que podría ser diferente en cada plataforma.

Espero que esto tenga sentido.

-1

Tal vez estoy simplificando demasiado el problema, pero acabo de verificar el nombre del proceso en el pasado.

Puede utilizar esta función para esperar hasta que el otro termine y luego ejecutar el proceso actual.

''' <summary> 
''' Is this app already running as a standalone exe? 
''' </summary> 
''' <returns></returns> 
''' <remarks> 
''' NOTE: The .vshost executable runs the whole time Visual Studio is open, not just when debugging. 
''' So for now we're only checking if it's running as a standalone. 
''' </remarks> 
public bool AlreadyRunning() 
{ 
    const string VS_PROCESS_SUFFIX = ".vshost"; 

    //remove .vshost if present 
    string processName = Process.GetCurrentProcess.ProcessName.Replace(VS_PROCESS_SUFFIX, string.Empty); 
    
    int standaloneInstances = Process.GetProcessesByName(processName).Length; 
    //Dim vsInstances As Integer = Process.GetProcessesByName(processName & VS_PROCESS_SUFFIX).Length 
    
    //Return vsInstances + standaloneInstances > 1 
    return standaloneInstances > 1; 
} 
+0

Su respuesta requiere que espere a que * todo el proceso * termine, por lo que es irrelevante para mi pregunta. – Timwi

Cuestiones relacionadas