How to find that Mutex in C# is acquired?

2019-02-16 05:53发布

问题:

How can I find from mutex handle in C# that a mutex is acquired?

When mutex.WaitOne(timeout) timeouts, it returns false. However, how can I find that from the mutex handle? (Maybe using p/invoke.)

UPDATE:

public class InterProcessLock : IDisposable
{
    readonly Mutex mutex;

    public bool IsAcquired { get; private set; }

    public InterProcessLock(string name, TimeSpan timeout)
    {
        bool created;
        var security = new MutexSecurity();
        security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));
        mutex = new Mutex(false, name, out created, security);
        IsAcquired = mutex.WaitOne(timeout);
    }

    #region IDisposable Members

    public void Dispose()
    {
        if (IsAcquired)
        {
            mutex.ReleaseMutex();
            IsAcquired = false;
        }
    }

    #endregion
}

Currently, I am using my own property IsAcquired to determine whether I should release a mutex. Not essential but clearer, would be not to use a secondary copy of the information represented by IsAcquired property, but rather to ask directly the mutex whether it is acquired by me. Since calling mutex.ReleaseMutex() throws an exception if it is not acquired by me.

(By acquired state I mean that the mutex is in not-signaled state when I am owning the mutex.)

(EDIT: I have added IsAcquired = false; thanks to mattdekrey's post.)

回答1:

As you may found, there are no public members on Mutex class: http://msdn.microsoft.com/en-us/library/system.threading.mutex_members.aspx

There is also no public native functions for that: http://msdn.microsoft.com/en-us/library/ms686360%28v=VS.85%29.aspx

However, there are some undocumented/unsupported functions especially in ntdll.dll. These allow accessing system objects. However, these functions may change or not be available in future versions of operating system.

So, the answer is: It is not possible using conventional means.



回答2:

The reason there is no clean way to do this is because it is not a good idea and the reason is because race conditions are -very- easy to introduce when you rely on this type of logic. So your design needs to change.

First, you should not acquire a lock in a constructor. Turn this class into a factory that returns a properly initialized mutex object. That way you can know if you acquired the lock or not.

DO NOT rely on Dispose to release locks, this is asking for deadlock ridden code that is hard to maintain. Use a try/finally block to ensure it is released.

Timeouts are a bit sketchy. Only use timeouts when not acquiring the lock would be considered normal operation. Not being able to acquire the lock is usually a bug and merely avoiding it with timeouts hides the bug. If you need timeouts, consider using an event (maybe AutoResetEvent), this may be more appropriate.



回答3:

Why can't you use Mutex.OpenExisting

try
{
    Mutex foundMutex = Mutex.OpenExisting("MyTestingMutex");

    // Found Mutex
    foundMutex.ReleaseMutex();
}
catch (System.Threading.WaitHandleCannotBeOpenedException)
{
    //   System.Threading.WaitHandleCannotBeOpenedException:
    //     The named mutex does not exist.
}

EDIT

I am guessing some of this.

It seems like you are trying to develop an API. One of the items you are offering in your API is an InterProcessLock.

I am going to assume you are sharing a collection across threads and you are using the Mutex to make sure only one operation is on it a time.

using (InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0)))
{
    if(myLock.IsAcquired)
    {
        // I have control then I can delete, add to the collection.
    }
}

I would reconsider this design. What if I never wraped InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0)) in a using? Dispose would not be called. What if the user never calls the Dispose at all?

There would be an abandoned Mutex

From MSDN

Caution An abandoned mutex often indicates a serious error in the code. When a thread exits without releasing the mutex, the data structures protected by the mutex might not be in a consistent state. The next thread to request ownership of the mutex can handle this exception and proceed, if the integrity of the data structures can be verified.

If you are trying to protect your users you might want to help them by controlling the Mutex for them so they never have to worry about it.

A possible example is

public static bool PerformLockedProcess(Action process, string commonLockName, TimeSpan timeout)
{
    Mutex mutex = null;

    // Get the Mutex for the User
    try
    {
        bool created;
        var security = new MutexSecurity();
        security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));

        mutex = new Mutex(false, commonLockName, out created, security);

        bool acquired = mutex.WaitOne(timeout);

        if (acquired)
        {
            process();

            return true;
        }

        return false;
    }
    finally
    {
        // Make sure we do not abandon the Mutex
        if (mutex != null)
        {
            try
            {
                mutex.ReleaseMutex();
            }
            catch (ApplicationException)
            {
                // In case that failes
            }
        }
    }
}

This is one possible way. It all depends on what the goal is. I would NOT relay on the final user to call a Dispose since a Mutex is an operating system construct. And if the name is not unquie it could effect other processes using the same mutex name.



回答4:

Well, it's not exactly what you're asking for, but I think it would solve your problem: why not just add some error handling specifically for the exception that occurs if the Mutex is aquired by someone else?

public void Dispose()
{
    if (IsAcquired)
        try
        { mutex.ReleaseMutex(); }
        catch (System.Threading.SynchronizationLockException)
        {
            // Handle the exception, assuming you need to do anything.
            // All other exceptions would still be passed up the stack.
        }
}


回答5:

This won’t benefit the original poster of the question but here it goes.

While I do not disagree with other posters on proper use of Mutexes, I had an application where I needed to test whether someone owns a mutex without taking ownership myself. As mentioned by others the only way is to use an undocumented NtQueryMutant system call from ntdll.dll. I created an extension method for the Mutex class that can be used like this:

        bool createdNew = true;
        var m = new Mutex(false, MutexName, out createdNew);
        if ( m != null)
        {
            int currentCount;
            bool ownedByCaller, abandonedState;
            if (m.TryQuery(out currentCount, out ownedByCaller, out abandonedState))
            {
                Console.WriteLine(string.Format("Created New: {3}, Count: {0}, OwvedByMe: {1}, Abandoned: {2}",
                    currentCount, ownedByCaller, abandonedState, createdNew));
            }
            m.Close();
        }

And here is the implementation

public static class MutexExtensionMethods
{
    public static bool TryQuery(this Mutex m, out int currentCount, out bool ownedByCaller, out bool abandonedState)
    {
        currentCount = -1;
        ownedByCaller = abandonedState = false;
        try
        {
            var handle = m.SafeWaitHandle;
            if (handle != null)
            {
                var h = handle.DangerousGetHandle();
                MutantBasicInformation mbi;
                int retLength;
                var ntStatus = NtQueryMutant(
                    h,
                    MutantInformationClass.MutantBasicInformation,
                    out mbi, 
                    Marshal.SizeOf(typeof(MutantBasicInformation)),
                    out retLength);
                GC.KeepAlive(handle); // Prevent "handle" from being collected before NtQueryMutant returns
                if (ntStatus == 0)
                {
                    currentCount   = mbi.CurrentCount;
                    ownedByCaller  = mbi.OwnedByCaller;
                    abandonedState = mbi.AbandonedState;
                    return true;
                }
            }
        }
        catch
        {
        }
        return false;
    }

    #region NTDLL.DLL

    [DllImport("ntdll.dll")]
    public static extern uint NtQueryMutant(
        [In] IntPtr MutantHandle,
        [In] MutantInformationClass MutantInformationClass,
        [Out] out MutantBasicInformation MutantInformation,
        [In] int MutantInformationLength,
        [Out] [Optional] out int ReturnLength
        );

    public enum MutantInformationClass : int
    {
        MutantBasicInformation
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MutantBasicInformation
    {
        public int CurrentCount;
        [MarshalAs(UnmanagedType.U1)]
        public bool OwnedByCaller;
        [MarshalAs(UnmanagedType.U1)]
        public bool AbandonedState;
    }

    #endregion

}


回答6:

.NET Mutex class is native mutex wrapper, which gives the same possibilities, as native mutex API (except waiting for number of waitable objects of different type). If you want to acquire mutex without blocking, call mutex.WaitOne(0). Using PInvoke, you can call WaitForSingleObject, with the same result.



回答7:

If you're really trying to do an inter-process lock, as the name implies, you will want a way to detect if the Mutex has actually been acquired anyway, correct? I'm not sure how your code that uses your InterProcessLock would be ensured to be locked if there was no IsAcquired property. (Also, to protect against programmers who accidentally call Dispose twice, I'd set the IsAcquired to false in your Dispose method.)

I've implemented the same thing myself (because I much prefer the using block to a try-finally just to release the mutex) and instead threw an exception when the timeout was exceeded, which, if I'm remembering the project correctly, did not call the Dispose method.

Edit: Added benefit of throwing the exception in the constructor: your critical section is also completely avoided, and you can perform error handling in the catch block, which could include the same method call your critical section had, anyway, though I personally would consider that a bad practice.

Upon further reflection, rather than using try ... catch as specified in another answer, you could use the following on your dispose:

public void Dispose()
{
    if (IsAcquired)
    {
        lock (mutex) 
        {
            mutex.ReleaseMutex();
            IsAcquired = false;
        }
    }
}

It feels a little ironic to lock a mutex, but there you have it. While I totally agree that you shouldn't rely on Dispose being called because of the documentation with the IDisposable interface, I think it is incredibly convenient to have an inter-process critical section indicated by a using() { } block.