Why doesn't Mutex get released when disposed?

后端 未结 9 1079
青春惊慌失措
青春惊慌失措 2020-11-29 02:22

I have the following code:

using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
       // Some code that dea         


        
相关标签:
9条回答
  • 2020-11-29 02:39

    If you need to garantee that the mutex is released switch to a try catch finally block and put the mutex release in the finally block. It is assumed that you own and have a handle for the mutex. That logic needs to be included before release is invoked.

    0 讨论(0)
  • 2020-11-29 02:41

    This design decision was made a long, long time ago. Over 21 years ago, well before .NET was ever envisioned or the semantics of IDisposable were ever considered. The .NET Mutex class is a wrapper class for the underlying operating system support for mutexes. The constructor pinvokes CreateMutex, the WaitOne() method pinvokes WaitForSingleObject().

    Note the WAIT_ABANDONED return value of WaitForSingleObject(), that's the one that generates the exception.

    The Windows designers put the rock-hard rule in place that a thread that owns the mutex must call ReleaseMutex() before it exits. And if it doesn't that this is a very strong indication that the thread terminated in an unexpected way, typically through an exception. Which implies that synchronization is lost, a very serious threading bug. Compare to Thread.Abort(), a very dangerous way to terminate a thread in .NET for the same reason.

    The .NET designers did not in any way alter this behavior. Not in the least because there isn't any way to test the state of the mutex other than by performing a wait. You must call ReleaseMutex(). And do note that your second snippet is not correct either; you cannot call it on a mutex that you didn't acquire. It must be moved inside of the if() statement body.

    0 讨论(0)
  • 2020-11-29 02:43

    The documentation explains (in the "Remarks" section) that there is a conceptual difference between instantiating a Mutex object (which does not, in fact, do anything special as far as synchronization goes) and acquiring a Mutex (using WaitOne). Note that:

    • WaitOne returns a boolean, meaning that acquiring a Mutex can fail (timeout) and both cases must be handled
    • When WaitOne returns true, then the calling thread has acquired the Mutex and must call ReleaseMutex, or else the Mutex will become abandoned
    • When it returns false, then the calling thread must not call ReleaseMutex

    So, there's more to Mutexes than instantiation. As for whether you should use using anyway, let's take a look at what Dispose does (as inherited from WaitHandle):

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

    As we can see, the Mutex is not released, but there is some cleanup involved, so sticking with using would be a good approach.

    As to how you should proceed, you can of course use a try/finally block to make sure that, if the Mutex is acquired, that it gets properly released. This is likely the most straightforward approach.

    If you really don't care about the case where the Mutex fails to be acquired (which you haven't indicated, since you pass a TimeSpan to WaitOne), you could wrap Mutex in your own class that implements IDisposable, acquire the Mutex in the constructor (using WaitOne() with no arguments), and release it inside Dispose. Although, I probably wouldn't recommend this, as this would cause your threads to wait indefinitely if something goes wrong, and regardless there are good reasons for explicitly handling both cases when attempting an acquire, as mentioned by @HansPassant.

    0 讨论(0)
  • 2020-11-29 02:43

    Reading the documentation for ReleaseMutex, it seems the design decision was that a Mutex should be released consciously. if ReleaseMutex isn't called, it signifies an abnormal exit of the protected section. putting the release in a finally or dispose, circumvents this mechanism. you are still free to ignore the AbandonedMutexException, of course.

    0 讨论(0)
  • 2020-11-29 02:54

    Ok, posting an answer to my own question. From what I can tell, this is the ideal way to implement a Mutex that:

    1. Always gets Disposed
    2. Gets Released iff WaitOne was successful.
    3. Will not get abandoned if any code throws an exception.

    Hopefully this helps someone out!

    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();
            }
        }
    }
    

    Update: Some may argue that if the code within the try block puts your resource in an unstable state, you should not release the Mutex and instead let it get abandoned. In other words, just call mut.ReleaseMutex(); when the code finishes successfully, and not put it within the finally block. The code acquiring the Mutex could then catch this exception and do the right thing.

    In my situation, I'm not really changing any state. I'm temporarily using a TCP port and can't have another instance of the program run at the same time. For this reason, I think my solution above is fine but yours may be different.

    0 讨论(0)
  • 2020-11-29 02:54

    Be aware: The Mutex.Dispose() executed by the Garbage collector fails because the garbage collection process does not own the handle according Windows.

    0 讨论(0)
提交回复
热议问题