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

时间:2014-08-21 17:40:18

标签: c# .net 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的句柄时,它是否应该自动释放?换句话说,如果我在ReleaseMutex区块中,为什么需要致电using

我现在还担心如果if区块内的代码崩溃,我会放弃躺在附近的互斥锁。

Mutex置于using块中是否有任何好处?或者,我应该 new up 一个Mutex实例,将其包装在try / catch中,并在 finally 块中调用ReleaseMutex()(基本上确切地实现我认为 Dispose()会做什么)

10 个答案:

答案 0 :(得分:52)

The documentation解释(在"备注和#34;部分中)实例化 Mutex对象(不,实际上,就同步而言,做一些特别的事情)和获取一个Mutex(使用WaitOne)。请注意:

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

因此,Mutexes比实例化还要多。至于你是否应该使用using,让我们来看看Dispose做什么(inherited from WaitHandle):

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

正如我们所看到的,互斥锁已发布,但涉及一些清理,因此坚持using将是一个很好的方法。

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

如果你真的不关心互联网无法获取的情况(你没有指明,因为你将TimeSpan传递给{ {1}}),您可以将WaitOne包装在您自己的实现Mutex的类中,在构造函数中获取Mutex(使用不带参数的IDisposable),并在{{1}内释放它1}}。虽然,我可能不会推荐这个,因为这会导致你的线程在出现问题时无限期地等待,并且无论是good reasons在尝试获取时都明确处理这两种情况,如@HansPassant所述

答案 1 :(得分:35)

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

注意WaitForSingleObject()的WAIT_ABANDONED返回值,它是生成异常的值。

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

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

答案 2 :(得分:7)

好的,回答我自己的问题。据我所知,这个是实现Mutex的理想方式:

  1. 始终获得处置
  2. 如果WaitOne成功,则发布已发布。
  3. 如果任何代码抛出异常,则不会被放弃。
  4. 希望这可以帮助别人!

    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块中。获取Mutex的代码可以捕获此异常并做正确的事情

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

答案 3 :(得分:7)

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

  1. 获取互斥
  2. 对对象进行更改,导致其状态无效
  3. 对对象进行更改,使其状态再次生效
  4. 释放互斥锁
  5. 如果在#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
    }
    

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

答案 4 :(得分:4)

我们需要了解更多.net以了解MSDN页面开头的内容,首先提示某人“奇怪”正在进行:

  

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

互斥锁是 Win32 命名对象”,每个进程按名称锁定,.net对象只是一个包装器回合Win32调用。 Muxtex本身位于Windows Kernal地址空间内,而不是您的应用程序地址空间。

在大多数情况下,如果您只是尝试在单个进程中同步对对象的访问,则最好使用Monitor

答案 5 :(得分:3)

如果您需要保证释放互斥锁,请切换到try catch finally块并将互斥锁释放到finally块中。假设您拥有并拥有互斥锁的句柄。在调用release之前,需要包含该逻辑。

答案 6 :(得分:2)

阅读ReleaseMutex的文档,似乎设计决定是应该有意识地释放互斥锁。如果未调用ReleaseMutex,则表示受保护部分的异常退出。将发布版本置于最终版本或处置中,绕开此机制。当然,你仍然可以忽略AbandonedMutexException。

答案 7 :(得分:2)

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

答案 8 :(得分:1)

Dispose取决于要发布的WaitHandle。因此,即使using调用Dispose,它也不会生效,直到满足稳定状态的条件。当您致电ReleaseMutex时,您告诉系统您正在释放该资源,因此可以自由处置该资源。

答案 9 :(得分:0)

最后一个问题。

<块引用>

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

如果不处理互斥对象,创建过多的互斥对象可能会遇到以下问题。

---> (Inner Exception #4) System.IO.IOException: Not enough storage is available to process this command. : 'ABCDEFGHIJK'
 at System.Threading.Mutex.CreateMutexCore(Boolean initiallyOwned, String name, Boolean& createdNew)
 at NormalizationService.Controllers.PhysicalChunkingController.Store(Chunk chunk, Stream bytes) in /usr/local/...

程序使用命名的互斥锁并在并行 for 循环中运行 200,000 次。
添加 using 语句解决了问题。