为什么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
块终止,我会在第二个进程中发现一个异常情况,告诉我们一个被废弃的互斥量。
解决方法是调用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的句柄时,它不应该被释放自动? 换句话说,如果我在using
块中,为什么还需要调用ReleaseMutex
?
我现在还担心,如果if
块中的代码崩溃,我会放弃躺在周围的互斥锁。
将Mutex
放入using
区块有什么好处吗? 或者,我应该只是新建一个Mutex
实例,将其包装在try / catch中,并在finally块中调用ReleaseMutex()
(基本上实现我认为Dispose()
会执行的操作)
文档解释了(在“备注”部分) 实例化 Mutex对象(实际上,就同步进行而言,事实上没有做任何特殊处理)和获取 Mutex(使用WaitOne
)之间存在概念上的区别。 注意:
WaitOne
返回一个布尔值,这意味着获取互斥锁可能会失败(超时),并且必须处理这两种情况 WaitOne
返回true
,那么调用线程已经获得了互斥体,并且必须调用ReleaseMutex
,否则互斥体将会被放弃 false
,调用线程不能调用ReleaseMutex
所以,Mutexes比实例化更多。 至于是否应该使用using
,无论如何,让我们来看看什么Dispose
呢(从继承WaitHandle
):
protected virtual void Dispose(bool explicitDisposing)
{
if (this.safeWaitHandle != null)
{
this.safeWaitHandle.Close();
}
}
正如我们所看到的,互斥体并未发布,但涉及到一些清理工作,因此坚持using
将是一种好方法。
至于你应该如何继续,你当然可以使用try/finally
块来确保,如果获得互斥量,它会被正确释放。 这可能是最直接的方法。
如果你真的不关心互斥体未能被获取的情况(你没有说明,因为你将TimeSpan
传递给WaitOne
),你可以将Mutex
包装在你自己的实现IDisposable
的类中,获取互斥锁构造函数(使用不带参数的WaitOne()
),并在Dispose
释放它。 尽管我可能不会推荐这样做,因为如果出现问题,这会导致线程无限期地等待,并且无论是否有充分理由在尝试获取时明确处理这两种情况(如@HansPassant所述)。
这个设计决定是很久以前做出的。 超过21年前,远在.NET之前,或IDisposable的语义曾经被考虑过。 .NET Mutex类是底层操作系统对互斥锁支持的封装类。 构造函数pinvokes CreateMutex,WaitOne()方法pinvokes WaitForSingleObject()。
请注意WaitForSingleObject()的WAIT_ABANDONED返回值,这是生成异常的那个值。
Windows设计人员采用了坚硬的规则,即拥有互斥锁的线程在退出之前必须调用ReleaseMutex()。 如果不是,这是一个非常强烈的迹象表明线程以意想不到的方式终止,通常是通过例外。 这意味着同步丢失了,这是一个非常严重的线程错误。 与Thread.Abort()相比,由于相同的原因,在.NET中终止一个线程非常危险。
.NET设计者并没有以任何方式改变这种行为。 一点都不重要,因为除了执行等待外,没有任何方法可以测试互斥锁的状态。 你必须调用ReleaseMutex()。 并注意你的第二个片段也是不正确的; 你不能通过你没有获得的互斥体来调用它。 它必须移入if()语句体内。
互斥体的主要用途之一是确保唯一能够看到处于不满足不变量状态的共享对象的代码是(有希望暂时)将对象置于该状态的代码。 需要修改对象的代码的正常模式是:
如果在#2开始之后和#3结束之前出现了问题,对象可能会处于不满足其不变量的状态。 由于正确的模式是在释放互斥体之前释放互斥体,因此代码在不释放互斥体的情况下处理互斥体这一事实意味着某处出现了问题。 因此,代码进入互斥体可能并不安全(因为它没有被释放),但没有理由等待互斥体被释放(因为 - 已经被处置 - 它永远不会) 。 因此,正确的行动方式是抛出异常。
一个比由.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
块内的代码不再需要访问该代码。 对于实现这种模式的库,我没有任何特别的建议,但作为其他类型的互斥体的包装并不是特别困难。
上一篇: Why doesn't Mutex get released when disposed?
下一篇: What is the fastest way for multiple threads to insert into a vector safely?