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

Mik*_*sen 53 .net c# 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
    }
}
Run Code Online (Sandbox Code Playgroud)

我在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();
}
Run Code Online (Sandbox Code Playgroud)

现在,事情按预期工作.

我的问题:通常关键IDisposable是它清理你放入的任何状态.我可以看到在一个块中可能有多个等待释放using,但是当处理Mutex的句柄时,它不应该自动释放吗?换句话说,ReleaseMutex如果我在一个using街区,为什么我需要打电话?

我现在也担心如果if块内的代码崩溃,我会放弃躺在那里的互斥锁.

投入Mutex一个using区块有什么好处吗?或者,我应该只是新建一个Mutex实例,将它包装在try/catch中,并ReleaseMutex()finally块中调用(基本上实现我想的 Dispose()那样)

voi*_*hos 54

该文档解释了(在"备注"部分中)实例化互斥对象(实际上,在同步过程中没有做任何特殊事情)和获取互斥(使用WaitOne)之间存在概念上的区别.注意:

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

因此,Mutexes不仅仅是实例化.至于你是否应该使用using,让我们看一下Dispose(继承自WaitHandle):

protected virtual void Dispose(bool explicitDisposing)
{
    if (this.safeWaitHandle != null)
    {
        this.safeWaitHandle.Close();
    }
}
Run Code Online (Sandbox Code Playgroud)

我们可以看到,Mutex 没有发布,但是有一些清理工作,所以坚持using这将是一个很好的方法.

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

如果你真的不关心Mutex无法获取的情况(你没有指明,因为你传递给TimeSpanWaitOne),你可以包装Mutex你自己的实现类,IDisposable在构造函数中获取Mutex(使用WaitOne()没有参数),并将其释放到里面Dispose.虽然,我可能不会推荐这个,因为如果出现问题,这会导致你的线程无限期地等待,并且无论是否有充分的理由在尝试获取时明确处理这两种情况,如@HansPassant所述.

  • `+ 1` - 很棒的答案!我认为如果`WaitOne`失败,你*不能*调用`ReleaseMutex`的事实意味着`Dispose`*不能*实现这个,除非它还实现逻辑来跟踪成功的等待.看起来解决方案是将`Mutex`放在using块中,并在`if`块中放置一个`try/finally`块. (7认同)
  • @MikeChristensen,如果它未能在超时时间内获取互斥体,它可以只是“抛出”,无论如何,这在我看来更有意义,因为它表明失败。 (2认同)

Han*_*ant 35

很久很久以前就做出了这个设计决定.超过21年前,早在.NET设想或IDisposable的语义被考虑之前..NET Mutex类是用于互斥锁的底层操作系统支持的包装类.构造函数pinvoke CreateMutex,WaitOne()方法pinvokes WaitForSingleObject().

注意WaitForSingleObject()的WAIT_ABANDONED返回值,即生成异常的返回值.

Windows设计人员制定了坚如磐石的规则,即拥有互斥锁的线程必须在退出之前调用ReleaseMutex().如果不是这样,这是一个非常强烈的迹象表明线程以意想不到的方式终止,通常是通过异常.这意味着同步丢失,这是一个非常严重的线程错误.与Thread.Abort()相比,这是一种非常危险的方法,在.NET中以相同的原因终止线程.

.NET设计者并没有以任何方式改变这种行为.至少是因为除了执行等待之外没有任何方法可以测试互斥锁的状态.您必须调用ReleaseMutex().并注意你的第二个片段也不正确; 你不能用你没有获得的互斥锁来调用它.它必须在if()语句体内移动.


Mik*_*sen 7

好的,发表我自己的问题的答案.据我所知,是实现这一目标的理想方式Mutex:

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

希望这可以帮助别人!

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:也许有人会说,如果内的代码try块把你的资源处于不稳定的状态,你应该不会释放互斥锁,而是让它抛弃.换句话说,只需mut.ReleaseMutex();在代码成功完成时调用,而不是将其放在finally块中.获取Mutex的代码可以捕获此异常并执行正确的操作.

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

  • @CodyGray我看到Mike在你的评论后添加了一个`catch`子句.但是不是try/finally块,没有`catch(){}`,好吧:异常可能会在堆栈中处理得更高,而不是吞没?(如果没有被捕获,程序会终止,并且释放互斥锁无关紧要!) (2认同)

sup*_*cat 7

互斥锁的主要用途之一是确保在不满足其不变量的状态下看到共享对象的唯一代码是(希望暂时)将对象置于该状态的代码.需要修改对象的代码的正常模式是:

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

如果在#2开始之后和#3完成之前出现问题,则对象可能处于不满足其不变量的状态.由于正确的模式是在释放互斥体之前释放互斥锁,因此代码在不释放它的情况下处理互斥锁这一事实意味着某些地方出现了问题.因此,代码进入互斥锁可能不安全(因为它尚未被释放),但是没有理由等待释放互斥锁(因为 - 已经被处置 - 它永远不会被释放) .因此,正确的行动方针是抛出异常.

比.NET互斥对象实现的模式稍微好一些的模式是让"获取"方法返回一个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
}
Run Code Online (Sandbox Code Playgroud)

如果内部代码在EnterDanger和之间失败LeaveDanger,则获取对象应通过调用Dispose它来使互斥锁无效,因为受保护资源可能处于损坏状态.如果内部代码在其他地方失败,则应该释放互斥锁,因为受保护的资源处于有效状态,并且using块中的代码将不再需要访问它.我没有任何特定的库实现该模式的建议,但实现其他类型的互斥体包装并不是特别困难.