Как найти, что Mutex в C # приобретен? - PullRequest
19 голосов
/ 10 июня 2010

Как узнать из дескриптора мьютекса в C #, что мьютекс получен?

При mutex.WaitOne(timeout) тайм-аутах возвращается false. Тем не менее, как я могу найти это из дескриптора мьютекса? (Возможно, используя 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
}

В настоящее время я использую собственное свойство IsAcquired, чтобы определить, следует ли мне освобождать мьютекс. Не существенно, но более понятно, было бы не использовать вторичную копию информации, представленной свойством IsAcquired, а скорее напрямую спрашивать мьютекс, получена ли она мной. Поскольку вызов mutex.ReleaseMutex() вызывает исключение, если оно не получено мной.

(Под приобретенным состоянием я подразумеваю, что мьютекс находится в незарегистрированном состоянии, когда я владею мьютексом.)

(РЕДАКТИРОВАТЬ: я добавил IsAcquired = false; благодаря сообщение Mattdekrey .)

Ответы [ 7 ]

13 голосов
/ 11 июня 2010

Причина, по которой нет чистого способа сделать это, заключается в том, что это не очень хорошая идея, а причина в том, что условия гонки очень легко представить, когда вы полагаетесь на этот тип логики.Поэтому ваш дизайн должен измениться.

Во-первых, вы не должны приобретать блокировку в конструкторе.Превратите этот класс в фабрику, которая возвращает правильно инициализированный объект мьютекса.Таким образом, вы можете узнать, приобрели ли вы блокировку или нет.

НЕ полагайтесь на Dispose для снятия блокировок, это требует кода, управляемого блокировкой, который трудно поддерживать.Используйте блок try / finally, чтобы убедиться, что он освобожден.

Тайм-ауты немного поверхностны.Используйте тайм-ауты только тогда, когда блокировка не получена, будет считаться нормальной работой.Неспособность получить блокировку, как правило, является ошибкой, и простое избежание ее с таймаутами скрывает ошибку.Если вам нужны тайм-ауты, рассмотрите возможность использования события (возможно, AutoResetEvent), это может быть более подходящим.

5 голосов
/ 22 июня 2010

Как вы можете найти, в классе Mutex нет открытых участников: http://msdn.microsoft.com/en-us/library/system.threading.mutex_members.aspx

Для этого также нет общедоступных собственных функций: http://msdn.microsoft.com/en-us/library/ms686360%28v=VS.85%29.aspx

Однако, есть некоторые недокументированные / неподдерживаемые функции, особенно в ntdll.dll. Они позволяют получить доступ к системным объектам. Однако эти функции могут измениться или быть недоступными в будущих версиях операционной системы.

Итак, ответ таков: невозможно использовать обычные средства.

2 голосов
/ 22 июня 2010

Почему вы не можете использовать 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

Я думаю, кое-что из этого.

Похоже, вы пытаетесь разработать API. InterProcessLock - это один из элементов, который вы предлагаете в своем API.

Я предполагаю, что вы делитесь коллекцией между потоками и используете Mutex, чтобы убедиться, что за один раз выполняется только одна операция.

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

Я бы пересмотрел этот дизайн. Что делать, если я никогда не завернул InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0)) в использование? Утилизация не будет называться. Что если пользователь вообще никогда не вызывает Dispose?

Там будет заброшенный мьютекс

С MSDN

Внимание! Заброшенный мьютекс часто указывает на серьезную ошибку в коде. Когда поток выходит без освобождения мьютекса, структуры данных, защищенные мьютексом, могут быть не согласованы. Следующий поток, запрашивающий владение мьютексом, может обработать это исключение и продолжить работу, если можно проверить целостность структур данных.

Если вы пытаетесь защитить своих пользователей, возможно, вы захотите помочь им, контролируя Mutex для них, чтобы им никогда не приходилось об этом беспокоиться.

Возможный пример:

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
            }
        }
    }
}

Это один из возможных способов. Все зависит от цели. Я бы НЕ передавал конечному пользователю вызов Dispose, поскольку Mutex - это конструкция операционной системы. И если имя не является лишним, это может повлиять на другие процессы, использующие то же имя мьютекса.

1 голос
/ 17 января 2012

Это не принесет пользы оригинальному постеру вопроса, но здесь это идет.

Хотя я не согласен с другими авторами по поводу правильного использования мьютексов, у меня было приложение, в котором мне нужно было проверить, владеет ли кто-нибудь мьютексом, не вступая в права собственности самостоятельно. Как уже упоминалось, единственный способ - использовать недокументированный системный вызов NtQueryMutant от ntdll.dll. Я создал метод расширения для класса Mutex, который можно использовать так:

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

А вот и реализация

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

}
1 голос
/ 10 июня 2010

Ну, это не совсем то, что вы просите, но я думаю, что это решит вашу проблему: почему бы просто не добавить некоторую обработку ошибок специально для исключения, которое происходит, если Mutex получен кем-то другим?

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.
        }
}
0 голосов
/ 11 июня 2010

Если вы действительно пытаетесь сделать межпроцессную блокировку, как следует из названия, вам понадобится способ определить, действительно ли Mutex был получен в любом случае, верно? Я не уверен, как ваш код, который использует ваш InterProcessLock, будет заблокирован, если бы не было свойства IsAcquired. (Также для защиты от программистов, которые случайно вызывают Dispose дважды, я бы установил IsAcquired в false в вашем Dispose методе.)

Я сам реализовал ту же самую вещь (потому что я очень предпочитаю использование блока вместо попытки завершить, просто чтобы освободить мьютекс) и вместо этого выдал исключение, когда тайм-аут был превышен, что, если я помню проект правильно, не вызывал метод Dispose.

Edit: Дополнительное преимущество в создании исключения в конструкторе: ваша критическая секция также полностью исключена, и вы можете выполнять обработку ошибок в блоке catch, который в любом случае может включать в себя тот же метод, который вызывал ваша критическая секция, хотя я лично учел бы это плохая практика.

При дальнейшем осмыслении, вместо использования try ... catch, как указано в другом ответе, вы можете использовать следующее в вашем распоряжении:

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

Это немного иронично для lock мьютекса, но вот оно у вас. Хотя я полностью согласен с тем, что вам не следует полагаться на вызов Dispose из-за документации с интерфейсом IDisposable, я думаю, что невероятно удобно иметь критическую секцию между процессами, обозначенную using() { } блоком.

0 голосов
/ 10 июня 2010

.NET Mutex класс - это встроенная оболочка мьютекса, которая предоставляет те же возможности, что и нативный API мьютекса (кроме ожидания количества ожидаемых объектов различного типа).Если вы хотите получить мьютекс без блокировки, вызовите mutex.WaitOne (0).Используя PInvoke, вы можете вызвать WaitForSingleObject с тем же результатом.

...