2017-03-09 2 views
1

Ich versuche, ein Mutex mit einer IDisposable Klasse wie folgt zu wickeln:Wrapping ein Mutex mit IDisposable und es zu testen, aber der Test endet nie

public class NamedMutex : IDisposable 
{ 
    private static readonly object _syncLock = new object(); 
    private readonly Mutex _namedMutex; 
    private readonly bool _createdNew; 

    public NamedMutex(string name) 
    { 
     if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); 
     //lock (_syncLock) 
     { 
      _namedMutex = new Mutex(initiallyOwned: false, name: name, createdNew: out _createdNew); 
     } 
     _namedMutex.WaitOne(); 
    } 

    public void Dispose() 
    { 
     //lock (_syncLock) 
     { 
      //if (_createdNew) 
      _namedMutex.ReleaseMutex(); 
      _namedMutex.Dispose(); 
     } 
    } 
} 

, wie Sie aus dem Kommentar gesetzt Code sehen ich habe habe so ziemlich alles ausprobiert, was ich mir vorstellen konnte, aber entweder ist mein Test falsch oder etwas stimmt nicht mit der obigen Implementierung, weil der Test nie endet (wahrscheinlich ein Deadlock, den ich nicht identifizieren kann oder mit dem es abstürzt) unsynchronisierte Ausnahme).

Dies ist mein Test, den ich für LINQPad angepasst:

void Main() 
{ 
    var sw = Stopwatch.StartNew(); 

    var task1 = Task.Run(async() => 
    { 
     using (new NamedMutex("foo")) 
     { 
      Console.WriteLine(3); 
      await Task.Delay(TimeSpan.FromSeconds(3)); 
     } 
    }); 

    var task2 = Task.Run(async() => 
    { 
     using (new NamedMutex("foo")) 
     { 
      Console.WriteLine(2); 
      await Task.Delay(TimeSpan.FromSeconds(2)); 
     } 
    }); 

    Task.WaitAll(task1, task2); 

    //Assert.IsTrue(sw.Elapsed.TotalSeconds >= 5); 
    sw.Elapsed.Dump(); // LINQPad 
} 
+0

Können Sie setzen einen Haltepunkt im Konstruktor von '' 'NamedMutex''' und uns sagen, ob es jemals bekommt hinter der' '' _namedMutex.WaitOne(); '' 'Linie? –

+0

@MattThomas ja es tut. Wenn ich 'Console.WriteLine (" WaitOne ") hinzufüge;' wird darunter 'WaitOne' nur einmal, dann sofort' 3' gedruckt und es bleibt für immer hängen. – t3chb0t

+0

Ich habe das gerade im Visual Studio getestet, und es funktioniert beim ersten Mal korrekt, aber alle nachfolgenden Versuche scheitern. Wenn Sie dann den Namen des Mutex ändern, funktioniert es erneut einmal und schlägt bei nachfolgenden Aufrufen erneut fehl. Es scheint, dass der Mutex nach dem Programmende nicht richtig entsorgt wird. –

Antwort

5

Dies wegen await passiert. Nach Ihrer await Task.Delay(..) Sie möglicherweise nicht mehr auf dem gleichen Thread waren Sie zuvor await Aussage. In einigen Fällen versuchen Sie also, Ihren Mutex aus dem Thread zu entfernen, der ihn nicht besitzt - daher Ihr Problem. Das ist einfach durch das Schreiben aktuellen Thread vor und nach await zu überprüfen:

class Program { 
    public static void Main() { 
     while (true) { 
      var sw = Stopwatch.StartNew(); 

      var task1 = Task.Run(async() => {      
       using (new NamedMutex("foo")) { 
        Console.WriteLine("first before await: " + Thread.CurrentThread.ManagedThreadId); 
        await Task.Delay(TimeSpan.FromSeconds(2)); 
        Console.WriteLine("first after await: " + Thread.CurrentThread.ManagedThreadId); 
       } 
      }); 

      var task2 = Task.Run(async() => {      
       using (new NamedMutex("foo")) { 
        Console.WriteLine("second before await: " + Thread.CurrentThread.ManagedThreadId); 
        await Task.Delay(TimeSpan.FromSeconds(1)); 
        Console.WriteLine("second after await: " + Thread.CurrentThread.ManagedThreadId); 
       } 
      }); 

      Task.WaitAll(task1, task2); 

      //Assert.IsTrue(sw.Elapsed.TotalSeconds >= 5); 
      Console.WriteLine(sw.Elapsed); 
     }    
    } 
} 
+0

Ich habe das gerade gemerkt, dann kam, um zu antworten und du hast mich dazu geschlagen. –

+0

Oh, das bedeutet wahrscheinlich, dass ich vergessen kann, einen 'Mutex' mit' async/await' zu verwenden? – t3chb0t

+0

Beachten Sie auch, dass dies vom aktuellen 'SynchronizationContext' abhängig ist. In einer UI-Anwendung wie WinForms oder WPF trat dieses Problem nicht auf, da ihr Kontext die Fortsetzungen zum UI-Thread marshale, die Konsolenanwendung jedoch nicht "main" -Thread zu marshalen, so wird die Fortsetzung nur auf einem beliebigen ThreadPool-Thread ausgeführt. –

1

Zur Erweiterung auf Evk's answer und zu einer Vermeidung des Problems zu bekommen, ist es immer noch möglich, eine Mutex mit einem IDisposable zu wickeln. Sie müssen nur sicherstellen, dass Sie die vollständige Kontrolle über die Thread haben, die die Mutex erwirbt und freigibt, und Sie müssen sicherstellen, dass der Kontext in diesem Thread zwischen Erwerb und Freigabe des Mutex nicht wechselt.

Also dreh einfach deinen eigenen Thread auf. Etwas wie:

class NamedMutex : IDisposable 
{ 
    private readonly Thread _thread; 
    private readonly ManualResetEventSlim _disposalGate; 
    private readonly Mutex _namedMutex; 
    public NamedMutex(string name) 
    { 
     var constructorGate = new ManualResetEventSlim(); 
     _disposalGate = new ManualResetEventSlim(); 
     _thread = new Thread(() => 
     { 
      // Code here to acquire the mutex 
      _namedMutex = new Mutex(initiallyOwned: false, name: name, createdNew: out _createdNew); 

      constructorGate.Set(); // Tell the constructor it can go on 
      _disposalGate.Wait(); // Wait for .Dispose to be called 

      // Code here to release the mutex 
      _namedMutex.ReleaseMutex(); 
      _namedMutex.Dispose(); 
     }); 
     _thread.Start(); 
     constructorGate.Wait(); 
    } 

    public void Dispose() 
    { 
     _disposalGate.Set(); 
    } 
} 
+0

Bitte beachten Sie meine [Kommentar] (http://stackoverflow.com/questions/42698844/wrapping-a-mutex-with-idisposable-and-testing-it-but-the-test-never-ends#comment72521003_42699138). Ich brauche den Namen, weil er die Ressource identifiziert, die zur Zeit zwei Prozesse nicht gleichzeitig ändern können. – t3chb0t

+0

@ t3chb0t Richtig. Das ist nur der Kern. Wirf den Namen durch den Konstruktor "' 'NamedMutex''' und erhalte den benannten Mutex, in dem der' '' // Code steht, um den Mutex''' Kommentar zu erhalten. Ich habe es bearbeitet, um diese Dinge hinzuzufügen –

+0

Dies scheint wie es funktionieren würde, obwohl es jedes Mal einen neuen Thread erstellen, der nicht billig ist. –

Verwandte Themen