2017-03-09 86 views
1

我想换一个MutexIDisposable类是这样的:结束语与IDisposable的互斥和测试,但测试永远不会结束

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

,你可以从注释掉的代码中,我看到尝试几乎所有我能想到的工作,但是要么是我的测试错误,要么是上面的实现不正确,因为测试要么永远不会结束(可能是一个死锁,我无法识别或崩溃,非同步异常)。

这是我的测试,我适合LINQPad:

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

你可以把一个断点'''NamedMutex'''的构造,并告诉我们,如果它曾经获得过'''_namedMutex.WaitOne();'''行? –

+0

@MattThomas是的。如果我在它下面添加'Console.WriteLine(“WaitOne”);'它只打印一次'WaitOne',然后立即'3'并且它永远挂起。 – t3chb0t

+0

我刚刚在visual studio中测试过它,它在第一次正常工作,但所有后续尝试都失败。然后,如果您更改互斥锁的名称,它将再次运行一次,然后再次失败。程序结束后,似乎互斥体没有正确处理。 –

回答

5

出现这种情况是因为await。在您的await Task.Delay(..)之后,您可能不再处于await声明之前的同一线程。所以在某些情况下,你试图从不拥有它的线程释放你的互斥体 - 因此你的问题。这很容易通过前,等待后面写当前线程验证:

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

我刚刚意识到这一点,然后来回答,你打败了我。 –

+0

哦,所以这可能意味着我可以忘记使用'异步/ await'的'互斥'? – t3chb0t

+0

另外,请注意,这取决于当前的'SynchronizationContext',在UI应用程序中,例如WinForms或WPF,由于这些问题的上下文将封送到UI线程的延续,所以不会发生此问题,但控制台应用程序没有“主”线程编组到,所以延续只是运行在任意的ThreadPool线程上。 –

1

要扩大Evk's answer,并得到一个解决方法,仍然可以包装一个MutexIDisposable。您只需确保您完全控制正在获取Mutex并释放它的Thread,并且您必须确保在获取和释放互斥锁之间上下文不会在该线程中切换。

所以,只需旋转自己的线程。喜欢的东西:

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

请参阅我的[评论](http://stackoverflow.com/questions/42698844/wrapping-a-mutex-with-idisposable-and-testing-it-but-the-test-never-ends#comment72521003_42699138)。我需要这个名字,因为它标识了当前两个进程无法同时修改的资源。 – t3chb0t

+0

@ t3chb0t对。这只是要点。所以通过NamedMutex'''构造函数将名称抛出并获取指定的互斥量,其中'''//代码在这里获取互斥量'注释。我编辑它来添加这些东西 –

+0

这似乎是可行的,但它确实涉及每次创建一个新线程,这并不便宜。 –