为什么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比实例化更多。 至于是否应该使用,让我们来看看Dispose
function( 从WaitHandle
inheritance ):
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状态的共享对象的代码是(希望暂时)将对象置于该状态的代码。 需要修改对象的代码的正常模式是:
- 获取互斥量
- 对导致其状态变为无效的对象进行更改
- 对导致其状态再次变为有效的对象进行更改
- 释放互斥体
如果在#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 }
如果内部代码在EnterDanger
和LeaveDanger
之间失败,那么捕获对象应该通过调用Dispose
来使互斥体失效,因为被保护的资源可能处于损坏状态。 如果内部代码在其他地方失败,则应该释放互斥锁,因为保护资源处于有效状态,并且using
块内的代码将不再需要访问它。 对于实现这种模式的库,我没有任何特别的build议,但是作为其他types的互斥体的包装并不是特别困难。
好的,发布一个答案我自己的问题。 从我所知道的情况来看, 这是实现Mutex
的理想方式:
- 总是得到处置
- 如果
WaitOne
成功,获得释放。 - 如果任何代码抛出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
你告诉系统你释放资源,因此可以自由处置它。