Подтвердить что ты не робот

Почему 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 завершается, я получаю исключение во втором процессе о заброшенном Mutex.

Исправление состоит в вызове 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? Или, должен ли я просто обновить экземпляр Mutex, обернуть его в try/catch и вызвать ReleaseMutex() в блоке finally (в основном реализовать то, что я думал Dispose())

4b9b3361

Ответ 1

В документации объясняется (в разделе "Замечания" ), что существует концептуальная разница между экземпляром Mutex объект (который фактически не делает ничего особенного для синхронизации) и приобретает Mutex (используя WaitOne). Обратите внимание:

  • WaitOne возвращает логическое значение, означающее, что получение Mutex может завершиться с ошибкой (timeout), и оба случая должны быть обработаны
  • Когда WaitOne возвращает true, тогда вызывающий поток приобрел Mutex и должен вызвать ReleaseMutex, иначе Mutex перестанет
  • Когда он возвращает false, тогда вызывающий поток не должен вызывать ReleaseMutex

Итак, там больше для мьютексов, чем для создания экземпляра. Что касается того, следует ли использовать using в любом случае, давайте посмотрим, что делает Dispose (как унаследовано от WaitHandle):

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

Как мы видим, Mutex не выпущен, но есть какая-то очистка, поэтому прилипание с using было бы хорошим подходом.

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

Если вам действительно не нравится случай, когда Mutex не может быть получен (который вы не указали, поскольку вы передаете TimeSpan в WaitOne), вы можете обернуть Mutex в свой собственный класс, который реализует IDisposable, получает Mutex в конструкторе (используя WaitOne() без аргументов) и освобождаю его внутри Dispose. Хотя, вероятно, я бы не рекомендовал этого, так как это приведет к тому, что ваши потоки будут ждать бесконечно, если что-то пойдет не так, и несмотря на то, что есть веские причины для явной обработки обоих случаев, когда пытаясь приобрести, как указано @HansPassant.

Ответ 2

Это дизайнерское решение было принято давно, давным-давно. Более 21 года назад, задолго до появления .NET, или когда-либо рассматривалась семантика IDisposable. Класс .NET Mutex является классом-оболочкой для базовой поддержки операционной системы для мьютексов. Конструктор pinvokes CreateMutex, метод WaitOne() выдает WaitForSingleObject().

Обратите внимание на возвращаемое WAIT_ABANDONED значение WaitForSingleObject(), которое генерирует исключение.

Дизайнеры Windows устанавливают твердое правило, в котором поток, которому принадлежит мьютекс , должен вызывать ReleaseMutex() до его выхода. И если это не значит, что это очень сильное указание на то, что поток неожиданно завершен, как правило, через исключение. Это означает, что синхронизация потеряна, очень серьезная проблема с потоками. Сравните с Thread.Abort(), очень опасным способом прервать поток в .NET по той же причине.

Разработчики .NET никоим образом не изменяли это поведение. Не в последнюю очередь потому, что нет никакого способа проверить состояние мьютекса, кроме как выполнить ожидание. Вы должны вызвать ReleaseMutex(). И обратите внимание, что ваш второй фрагмент не является правильным; вы не можете называть это мьютексом, который вы не получили. Он должен быть перемещен внутри тела оператора if().

Ответ 3

Хорошо, отправляя ответ на мой собственный вопрос. Из того, что я могу сказать, это идеальный способ реализовать Mutex, который:

  • Всегда получает Disposed
  • Получен выпуск iff WaitOne.
  • Не будет отказано, если какой-либо код генерирует исключение.

Надеюсь, это поможет кому-то!

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 помещает ваш ресурс в состояние нестабильности, вы не должны отпускать Mutex и вместо этого оставлять его заброшенным. Другими словами, просто позвоните mut.ReleaseMutex();, когда код завершится успешно, а не поместите его в блок finally. Код, приобретающий Mutex, мог бы поймать это исключение и сделать правильную вещь.

В моей ситуации я не меняю никакого состояния. Я временно использую TCP-порт и не могу одновременно запускать другой экземпляр программы. По этой причине, я думаю, что мое решение выше прекрасно, но ваши могут отличаться.

Ответ 4

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

  • Получение мьютекса
  • Внести изменения в объект, из-за которого его состояние становится недействительным.
  • Внести изменения в объект, который заставляет его состояние снова стать действительным.
  • Отключение мьютекса

Если что-то пойдет не так после того, как №2 началось и до того, как №3 закончит, объект может быть оставлен в состоянии, которое не удовлетворяет его инвариантам. Поскольку правильный шаблон заключается в том, чтобы освободить мьютекс перед его удалением, тот факт, что код предоставляет мьютекс, не выпуская его, означает, что что-то пошло не так. Таким образом, может быть небезопасно вводить код в мьютексе (поскольку он еще не был выпущен), но нет причин ждать выхода мьютекса (поскольку - если он был удален - он никогда не будет), Таким образом, правильный ход действий - это исключение.

Шаблон, который несколько лучше, чем тот, который реализован объектом mutex.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
}

Если внутренний код выходит из строя между EnterDanger и LeaveDanger, тогда объект сбора должен аннулировать мьютекс, вызывая Dispose на нем, поскольку охраняемый ресурс может находиться в поврежденном состоянии. Если внутренний код выходит из строя в другом месте, мьютекс должен быть освобожден, так как охраняемый ресурс находится в допустимом состоянии, а код в блоке using больше не понадобится ему. У меня нет каких-либо конкретных рекомендаций библиотек, реализующих этот шаблон, но это не особенно сложно реализовать в качестве обертки вокруг других видов мьютекса.

Ответ 5

Нам нужно понять больше .net, чтобы знать, что происходит в начале страницы MSDN, дает первый намек на то, что кто-то "странно" происходит:

примитив синхронизации, который также может использоваться для interprocessсинхронизации.

Mutex является Win32 " Именованный объект", каждый процесс блокирует его по имени, объект .net - это всего лишь оболочка раунда вызовы Win32. Сам Muxtex живет в адресном пространстве Windows Kernal, а не в адресном пространстве вашего приложения.

В большинстве случаев вам лучше использовать Monitor, если вы только пытаетесь синхронизировать доступ к объектам в рамках одного процесса.

Ответ 6

Если вам нужно гарантировать, что мьютекс выпущен, переключитесь на блок finally try catch и поместите релиз mutex в блок finally. Предполагается, что вы владеете и имеете ручку для мьютекса. Эта логика должна быть включена до вызова release.

Ответ 7

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

Ответ 8

Знайте: Mutex.Dispose(), выполняемый сборщиком Garbage, терпит неудачу, потому что процесс сбора мусора не владеет дескриптором в соответствии с Windows.

Ответ 9

Dispose зависит от WaitHandle. Таким образом, даже если using вызывает Dispose, он не будет вдаваться в силу до тех пор, пока не будут выполнены условия стабильного состояния. Когда вы вызываете ReleaseMutex, вы сообщаете системе, что вы освобождаете ресурс, и, таким образом, он может свободно распоряжаться им.