为什么Mutex在处置时不被释放?

我有以下代码:

using (Mutex mut = new Mutex(false, MUTEX_NAME)) { if (mut.WaitOne(new TimeSpan(0, 0, 30))) { // Some code that deals with a specific TCP port // Don't want this to run at the same time in another process } } 

我已经在if块中设置了一个断点,并在Visual Studio的另一个实例中运行相同的代码。 正如所料, .WaitOne呼叫阻止。 然而,令我吃惊的是,一旦我继续执行 ,并且using块终止,我在第二个进程中得到一个关于一个被废弃的Mutex的exception。

解决的办法是调用ReleaseMutex

 using (Mutex mut = new Mutex(false, MUTEX_NAME)) { if (mut.WaitOne(new TimeSpan(0, 0, 30))) { // Some code that deals with a specific TCP port // Don't want this to run twice in multiple processes } mut.ReleaseMutex(); } 

现在,事情按预期工作。

我的问题:通常情况下,一个IDisposable在于它清理了你所处的任何状态。我可以看到在一个using块中可能有多个等待释放 ,但是当处理Mutex的时候,它不应该被释放自动? 换句话说,为什么我需要调用ReleaseMutex如果我在一个using块?

我现在也担心,如果if块中的代码崩溃,我会放弃躺在周围的互斥体。

Mutex放入using区块有什么好处吗? 或者,我应该只是新build一个Mutex实例,将其包装在try / catch中,并在finally块中调用ReleaseMutex() (基本上实现我认为 Dispose()会做的事)

这个文档解释了(在“注释”部分) 实例化一个Mutex对象(事实上,在同步进行的时候并不做任何特殊的事情)和获取一个Mutex(使用WaitOne )之间有一个概念上的区别。 注意:

  • WaitOne返回一个布尔值,这意味着获取互斥锁可能会失败 (超时),并且必须处理这两种情况
  • WaitOne返回true ,调用线程已经获得了Mutex,并且必须调用ReleaseMutex ,否则Mutex将被废弃
  • 当它返回false ,调用线程不能调用ReleaseMutex

所以,Mutexes比实例化更多。 至于是否应该使用,让我们来看看Disposefunction( 从WaitHandleinheritance ):

 protected virtual void Dispose(bool explicitDisposing) { if (this.safeWaitHandle != null) { this.safeWaitHandle.Close(); } } 

正如我们所看到的,互斥体没有被释放,但有一些清理,所以坚持using将是一个很好的方法。

至于你应该如何继续,你当然可以使用try/finally块来确保,如果获得互斥量,它会被正确释放。 这可能是最直接的方法。

如果你真的不关心互斥体未能被获取的情况(你没有指出,因为你将TimeSpan传递给了WaitOne ),你可以在你自己的类中包装Mutex来实现IDisposable ,获取互斥体构造函数(使用不带参数的WaitOne() ),并在Dispose释放它。 虽然,我可能不会推荐这样做,因为如果出现错误,这会导致线程无限期地等待,并且无论是否有充分的理由明确地处理这两种情况,如@HansPassant所提到的那样。

这个devise决定是很久以前的事了。 超过21年前,远在.NET之前,IDisposable的语义曾经被考虑过。 .NET Mutex类是底层操作系统对互斥锁支持的封装类。 构造函数pinvokes CreateMutex ,WaitOne()方法pinvokes WaitForSingleObject() 。

请注意WaitForSingleObject()的WAIT_ABANDONED返回值,这是生成exception的那个值。

Windowsdevise人员将坚硬的规则放置在拥有互斥锁的线程必须在退出之前调用ReleaseMutex()。 如果这不是一个非常强烈的迹象表明线程以意想不到的方式终止,通常是通过一个例外。 这意味着同步丢失,一个非常严重的线程错误。 与Thread.Abort()相比,由于相同的原因,在.NET中终止一个线程是非常危险的方法。

.NETdevise者并没有以任何方式改变这种行为。 一点都没有,因为除了执行等待之外,没有任何方法可以testing互斥锁的状态。 你必须调用ReleaseMutex()。 并注意你的第二个片段也是不正确的; 你不能通过你没有获得的互斥体来调用它。 它必须移动到if()语句的内部。

互斥体的主要用途之一是确保唯一能够看到处于不满足不variables状态的共享对象的代码是(希望暂时)将对象置于该状态的代码。 需要修改对象的代码的正常模式是:

  1. 获取互斥量
  2. 对导致其状态变为无效的对象进行更改
  3. 对导致其状态再次变为有效的对象进行更改
  4. 释放互斥体

如果在#2开始之后和#3结束之前出现了问题,则可能会使对象处于不满足其不variables的状态。 由于正确的模式是在释放互斥之前释放一个互斥,因此代码在不释放互斥的情况下释放一个互斥,这意味着某处出现了错误。 因此,代码进入互斥体可能并不安全(因为它没有被释放),但是没有理由等待互斥体被释放(因为 – 已经被处置 – 它永远不会) 。 因此,正确的做法是抛出exception。

一个比由.NET互斥对象实现的模式更好的模式是让“acquire”方法返回一个IDisposable对象,它不封装互斥对象,而是封装一个特定的对象。 处理该对象然后将释放互斥体。 代码可以看起来像这样:

 using(acq = myMutex.Acquire()) { ... stuff that examines but doesn't modify the guarded resource acq.EnterDanger(); ... actions which might invalidate the guarded resource ... actions which make it valid again acq.LeaveDanger(); ... possibly more stuff that examines but doesn't modify the resource } 

如果内部代码在EnterDangerLeaveDanger之间失败,那么捕获对象应该通过调用Dispose来使互斥体失效,因为被保护的资源可能处于损坏状态。 如果内部代码在其他地方失败,则应该释放互斥锁,因为保护资源处于有效状态,并且using块内的代码将不再需要访问它。 对于实现这种模式的库,我没有任何特别的build议,但是作为其他types的互斥体的包装并不是特别困难。

好的,发布一个答案我自己的问题。 从我所知道的情况来看, 是实现Mutex的理想方式:

  1. 总是得到处置
  2. 如果WaitOne成功,获得释放。
  3. 如果任何代码抛出exception,不会被抛弃。

希望这可以帮助别人!

 using (Mutex mut = new Mutex(false, MUTEX_NAME)) { if (mut.WaitOne(new TimeSpan(0, 0, 30))) { try { // Some code that deals with a specific TCP port // Don't want this to run twice in multiple processes } catch(Exception) { // Handle exceptions and clean up state } finally { mut.ReleaseMutex(); } } } 

更新:有些人可能会争辩说,如果try块内的代码使你的资源处于不稳定状态,你不应该释放互斥体,而是让它被抛弃。 换句话说,只需调用mut.ReleaseMutex(); 当代码成功完成,而不是把它放在finally块。 获取互斥锁的代码可以捕获这个exception,并做正确的事情

在我的情况下,我并没有真正改变任何状态。 我暂时使用TCP端口,不能同时运行程序的另一个实例。 出于这个原因,我认为我的解决scheme以上是好的,但你可能会有所不同。

我们需要了解更多然后.net才能知道MSDN页面开始时发生了什么,给出了有人“奇怪”正在进行的第一个提示:

同步原语也可用于进程间同步。

互斥锁是一个Win32的命名对象 ”,每个进程都通过名称来locking它,.net对象只是Win32调用的一个包装。 Muxtex本身位于Windows Kernal地址空间内,而不是您的应用程序地址空间。

在大多数情况下,如果您只是试图在一个进程内同步对象的访问,那么使用Monitor最好。

如果你需要保证互斥体被释放,就切换到try catch finally块,并把互斥量释放到finally块中。 假设你拥有并拥有一个互斥体的句柄。 在调用发布之前,需要包含该逻辑。

阅读ReleaseMutex的文档,似乎devise决定是应该有意识地释放Mutex。 如果ReleaseMutex未被调用,则表示被保护部分的exception退出。 最终放置或释放,绕开这个机制。 当然,您仍然可以自由地忽略AbandonedMutexException。

请注意:由垃圾收集器执行的Mutex.Dispose()失败,因为垃圾收集进程不拥有根据Windows的句柄。

依赖于WaitHandle来释放。 所以,即使using调用Dispose ,也不会影响稳定状态的条件。 当你调用ReleaseMutex你告诉系统你释放资源,因此可以自由处置它。