2010-02-24 4 views
7

Wenn Sie zwei Threads in einer Anwendung haben, und Sie wollen, dass sie nicht gleichzeitig ein bestimmtes Stück Code ausführen können, können Sie nur eine Sperre setzen um das Stück Code, wie folgt aus:C#: Wie kann verhindert werden, dass zwei Instanzen einer Anwendung gleichzeitig dasselbe tun?

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

Aber Wie macht man dasselbe in verschiedenen Prozessen? Ich dachte, das ist das, was man einen „globalen Mutex“ verwenden für, so dass ich versuchte, die Mutex Klasse auf verschiedene Weise, aber es scheint nicht meine Anforderungen zu erfüllen, die da sind:

  • Wenn Sie der einzige sind Beispiel, gehen Sie voran und führen Sie den Code aus.
  • Wenn Sie die zweite Instanz sind, warten Sie, bis die erste abgeschlossen ist, und führen Sie den Code aus.
  • Keine Ausnahmen auslösen. Ich

Probleme lief in:

  • einfach ein Mutex Objekt in einer using(){...} Klausel Instanziieren scheint nichts zu tun; die beiden Instanzen immer noch glücklich gleichzeitig
  • Aufruf von .WaitOne() auf dem Mutex bewirkt, dass die erste Instanz ausgeführt wird und die Sekunde zu warten, aber die zweite wartet unendlich, auch nach den ersten Aufrufen .ReleaseMutex() und verlässt den Bereich using(){}.
  • .WaitOne() löst eine Ausnahme aus, wenn der erste Prozess beendet wird (System.Threading.AbandonedMutexException).

Wie löse ich das? Lösungen, die Mutex nicht betreffen, sind sehr willkommen, zumal Mutex scheint Windows-spezifisch zu sein.

+3

Sie verwenden .NET und beschweren sich darüber, dass 'Mutex' windowsspezifisch ist? –

+2

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

+1

@Randolpho: Wer ist Ziel, die gesamte .NET-Bibliothek zu implementieren. Einschließlich 'System.Threading.Mutex'. Wenn es Teil der .NET-Bibliothek ist, können Sie erwarten, dass es auf jeder .NET-Plattform ausgeführt wird (einschließlich mono, sobald es diesen Teil implementiert hat). –

Antwort

5

Ich habe zwei Anwendungen:

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

ConsoleApplication1 starten, gefolgt von ConsoleAplication2 funktioniert perfekt ohne Fehler. Wenn Ihr Code immer noch bombardiert, ist es ein Bug mit Ihrem Code, nicht die Mutex-Klasse.

+0

Vielen Dank. Ich hätte schwören können, dass dies genau das ist, was ich getan habe, aber ich muss etwas anders gemacht haben, weil es nicht funktioniert hat und jetzt tut es es. Danke noch einmal. – Timwi

4

Ich habe erfolgreich Mutex für genau diesen Zweck verwendet und kann bestätigen, dass es funktioniert, obwohl es ein paar Macken gab.

Ich habe einen vollständigen Arbeitscode Beispiel zu Hause. Schreiben Sie einfach einen Kommentar zu dieser Antwort, wenn Sie möchten, dass ich heute Abend das Code-Beispiel hinzufüge.

UPDATE:

Hier ist die abgespeckte Code aus meiner Produktion App. Dies ist eine Konsolenanwendung, aber derselbe Prinzipal sollte für jeden Anwendungstyp gelten. Führen Sie mit einem Befehlszeilenargument von

--mutex 

die Mutex-Logik zu testen.

Beachten Sie, dass der Mutex in meinem Fall den Großteil des Prozesses wirklich schützt, aber es gibt keinen Grund, dass Sie ihn so verwenden müssen. Genau das war es in diesem Fall.

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: Ich werde den Code heute Abend veröffentlichen. Ich habe mir ein bisschen den Kopf gekratzt, um das zu schaffen, aber jetzt wird es ohne Probleme in einer Großserien-App verwendet. –

+0

danke Kumpel .. am hilfreichsten. – baash05

+0

das könnte schließlich abstürzen .. wenn der Mutex Null ist, dann würde schließen nicht funktionieren .. Ich bin mir nicht sicher, wie es Null sein könnte, aber wenn es nicht möglich ist, dann wäre der Test für null nicht erforderlich. – baash05

4
+0

Ja ein benannter Mutex ist die Art, wie Prozesse dieses Windows-Primitiv teilen können. –

+0

Ich habe. Es hat nicht funktioniert. Ein funktionierender Beispielcode wäre nett. – Timwi

+1

@Timwi - Können Sie den Code angeben, den Sie verwenden, der nicht funktioniert? Es kann sein, dass Sie etwas anderes tun, was dazu führt, dass dies fehlschlägt. – Nick

0

Obwohl ich empfehlen würde, Mutex zu verwenden und zu untersuchen, ob das Problem Ihr Code selbst ist oder nicht, könnte eine sehr komplexe, sehr fragile Alternative die Verwendung von "Dateien sperren" sein.

Grundsätzlich erstellen Sie eine temporäre Datei im Dateisystem mit einem Namen, den beide Prozesse erkennen.Wenn die Datei existiert, ist die Sperre vorhanden. Der andere Prozess sollte alle anderen Sperren freigeben, warten und versuchen, sie erneut zu erfassen. Wenn die Datei nicht existiert, ist die Sperre nicht vorhanden. Der Prozess sollte die Datei erstellen, um die Sperre abzuschließen und die Verarbeitung fortzusetzen, wobei die Datei gelöscht wird, wenn die Sperre aufgehoben wird.

Wie gesagt, es ist sehr komplex und zerbrechlich, aber es verwendet nicht Mutex.

Alternativ verwenden Sie Mutex.

0

Wenn Sie keinen Mutex verwenden möchten, würde ich sagen, rollen Sie Ihre eigenen mit sagen, eine Datei, die existiert, wäre eine einfache Lösung. Wenn die Datei vorhanden ist, starten Sie keine neue Datei. Sie müssen jedoch die Ausnahmefälle behandeln, bei denen ein Prozess vorzeitig beendet wird und die Datei nicht bereinigt wird.

Zurück zum Mutex für einen Moment, dies ist nur ein Betriebssystem "Objekt" für einen besseren Begriff. Sie brauchen wirklich so etwas auf jedem Betriebssystem, das Sie verwenden. Ich bin sicher, dass die anderen .net clrs Ihnen erlauben werden, auf diese Systemgrundelemente auch zuzugreifen. Ich würde einfach jeden mit einer definierten Assembly einpacken, die auf jeder Plattform anders sein könnte.

Hope das macht Sinn.

-1

Vielleicht bin ich zu einfach das Problem zu vereinfachen, aber ich habe gerade den Prozessnamen in der Vergangenheit überprüft.

Sie könnten einfach diese Funktion verwenden, um zu warten, bis der andere fertig ist und dann den aktuellen Prozess ausführen.

''' <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

Ihre Antwort erfordert, dass ich darauf warte, dass der * gesamte Prozess * beendet ist, was für meine Frage irrelevant ist. – Timwi