Нужно побить GC и уничтожить объект, как только он выйдет из области видимости - PullRequest
4 голосов
/ 04 февраля 2010

У меня есть несколько разделов кода, которые мне нужно защитить с помощью Mutex. Проблема в том, что код выглядит примерно так:

lock(mylockobject) {
  if(!foo())
    throw new MyException("foo failed");
  if(!bar())
    throw new MyException("bar failed");
}

Используя блокировку, она работает так, как я хотел, но теперь мне нужно использовать мьютекс. Очевидная проблема здесь заключается в том, что если я получу mutex и foo () или bar () не получится, мне придется полностью освободить мьютекс, прежде чем выдавать каждое исключение.

В C ++ я использовал бы область видимости объекта, созданного в стеке, и заблокировал бы мьютекс в конструкторе объекта, а затем освободил его в деструкторе. С сборкой мусора в .NET я не думал, что это сработает. Я написал тестовое приложение и подтвердил, что если я сделаю что-то вроде этого:

public class AutoMutex
{
  private Mutex _mutex;
  public AutoMutex(Mutex mutex)
  {
     _mutex = mutex;
     _mutex.WaitOne();
  }

  ~AutoMutex()
  {
    _mutex.ReleaseMutex();
  }
}

и затем иметь такой код:

// some code here...
Mutex my_mutex = new Mutex(false, "MyMutex");
{ // scoping like I would do in C++
  AutoMutex test = new AutoMutex(my_mutex);
  test = null;
}

Деструктор (финализатор?) Вызывается не намного позже.

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

Ответы [ 8 ]

16 голосов
/ 04 февраля 2010

Пара очков.

1) То, что вы хотите найти, это «одноразовый шаблон». Будьте очень осторожны, чтобы правильно реализовать . Конечно, Mutex уже реализует одноразовый шаблон, поэтому мне неясно, почему вы хотите создать свой собственный, но все же, о нем полезно узнать.

См. Этот вопрос для некоторых дополнительных мыслей о том, разумно ли использовать одноразовый шаблон, как если бы это был RAII:

Неправильно ли использовать IDisposable и «использование» в качестве средства для получения «ограниченного поведения» для обеспечения безопасности исключений?

2) Try-finally также имеет семантику, которую вы хотите. Конечно, блок "using" - это просто синтаксический сахар для try-finally.

3) Вы уверены, , что хотите освободить мьютекс, когда что-то выбрасывается? Вы уверены, что хотите бросить в защищенном регионе?

Это неприятный запах кода по следующей причине.

Почему у вас мьютекс? Обычно, потому что шаблон выглядит так:

  • состояние соответствует, но устарел
  • блокировка доступа к состоянию
  • делает государство непоследовательным
  • согласовать состояние
  • разблокировать доступ к состоянию
  • состояние теперь согласованно и свежо

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

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

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

Шаблон, который вы действительно должны реализовать:

  • состояние соответствует, но устарел
  • блокировка доступа к состоянию
  • делает государство непоследовательным
  • согласовать состояние
  • если возникает исключение, откатитесь в устаревшее согласованное состояние
  • разблокировать доступ к состоянию
  • состояние теперь соответствует и, если не было исключения, обновляется

Это гораздо более безопасная альтернатива, но писать код, который выполняет подобные транзакции, сложно. Никто не говорил, что многопоточность - это просто.

12 голосов
/ 04 февраля 2010

Чтобы обеспечить обзор, вы можете сделать AutoMutex агрегат IDisposable и использовать его следующим образом:

using(new AutoMutex(.....))
{
  if(!foo())
    throw new MyException("foo failed");
  if(!bar())
    throw new MyException("bar failed");
}    

В вашей реализации IDisposable.Dispose() освободите мьютекс.

9 голосов
/ 04 февраля 2010

Mutex реализует IDisposable, поэтому оберните его в using

using (Mutex m = new Mutex())
{
   //Use mutex here

}
//Mutex is out of scope and disposed
7 голосов
/ 04 февраля 2010

Используйте блок try / finally или шаблон IDisposable и включите его в оператор использования.

3 голосов
/ 04 февраля 2010

ГХ не детерминирован.Кроме того, он ведет себя по-разному в режиме отладки, что делает его немного более запутанным для работы в среде разработки.

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

public class AutoMutex : IDisposable
{
    private Mutex _mutex;  
    public AutoMutex(Mutex mutex)
    {
       _mutex = mutex;
       _mutex.WaitOne();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    void Dispose(bool disposing)
    {
        // method may be called more than once from different threads
        // you should avoid exceptions in the Dispose method
        var victim = _mutex;
        _mutex = null;
        if(victim != null)
        {
          victim.ReleaseMutex();
          victim.Dispose();
        }
        if(disposing)
        {
          // release managed resources here
        }
    }
}

и используется

using(new AutoMutex(new Mutex(false, "MyMutex")))
{
  // whatever within the scope of the mutex here
}
3 голосов
/ 04 февраля 2010

Я думаю, что у всех вас есть утилизация / использование, но вот пример использования try / finally:


Mutex m = new Mutex()
m.WaitOne();
try
{
  if(..)
    throw new Exception();              
}
finally
{
  // code in the finally will run regardless of whether and exception is thrown.
  m.ReleaseMutex();
}


1 голос
/ 04 февраля 2010

Почему бы вам не заключить код в Foo () в try-catch-finally, а затем, наконец, освободить мьютекс? (Улов может перебрасывать любые исключения, при желании оборачивая их в пользовательский тип исключения.)

0 голосов
/ 04 февраля 2010

Для действия на основе контекста идиома C # - try...finally. Это будет выглядеть так:

try {
    acquire the mutex
    do your stuff which may fail with an exception
} finally {
    release the mutex
}

Использование экземпляров объектов для получения ресурсов - это C ++, который исходит из способности C ++ обрабатывать экземпляры со сроком службы, ограниченным текущей областью действия. В языках, где экземпляры размещаются в куче и асинхронно уничтожаются сборщиком мусора (C #, Java), деструкторы (или финализаторы) не подходят для этого. Вместо этого для выполнения действия на основе области используется конструкция finally.

...