2010-02-04 2 views
4

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

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 еще не указал мне в правильном направлении, но я все еще работаю над этим ... Пожалуйста, дайте мне знать, как вы можете решить эту небольшую проблему, если это даже возможно.

+1

Название шаблона в C++ здесь «Получение ресурса есть инициализация» и часто упоминается как «RAII.» Если вы ищете «RAII в C#», вы найдете, например, http://blogs.msdn.com/colinth/archive/2007/08/08/c-for-c-users-a-doesn-t -act-как-destructor.aspx. В C# мы вызываем «одноразовый шаблон», и вы можете читать, например, http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx. Вдоль этих строк вы также должны прочитать «try-finally» http://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx. Наконец, не бросайте внутрь охраняемых регионов вещи, находящиеся в плохом состоянии. – jason

+0

спасибо за ссылки! – Dave

ответ

12

Для того, чтобы обеспечить область видимости, вы можете сделать свой AutoMutex реализовать IDisposable и использовать его как это:

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

В ваша реализация IDisposable.Dispose(), отпустите мьютексы.

+0

Хотя все предлагали очень похожие советы, Марсель, ваш это ответ, который отлично сработал для меня. Теперь мой AutoMutex работает точно так, как я ожидал! Благодаря! – Dave

9

мьютекса реализует IDisposable так обернуть его в using

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

} 
//Mutex is out of scope and disposed 
+0

@Oded: Если «is is not» вы имеете в виду «Mutex» не реализует «IDispoable», тогда вы ошибаетесь. 'Mutex' наследует от' WaitHandle', который реализует 'IDisposable'. – jason

+0

@ Oded-- это потому, что он расширяет WaitHandle и WaitHandle реализует IDisposable. Однако здесь не так много пользы, если AutoMutex не расширяет Mutex, а использует его с помощью композиции. – tvanfosson

+2

Это, Mutex расширяет Waithandle, который реализует IDisposable. Yay наследство! –

7

Используйте попытку /, наконец, блокировать или использовать IDisposable шаблон и обернуть использование в использовании заявлении.

+0

Я попробую! Похоже, у многих людей есть одно и то же предложение. Я рад, что это также открыло мне глаза и теперь есть представление о том, как справиться с такой ситуацией в будущем (т. Е. Следовать пути наследования к таким вещам, как IDisposable). – Dave

1

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

0

Для действия на основе области действия идиома 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 используется для выполнения действия на основе области.

3

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

 

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

 
+0

хорошая альтернатива с одним таймером –

16

Пара очков.

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

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

Is it abusive to use IDisposable and "using" as a means for getting "scoped behavior" for exception safety?

2) Примеркой, наконец, также имеет семантику вы хотите. Конечно, «используемый» блок - это просто синтаксический сахар для try-finally.

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

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

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

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

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

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

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

Узор вы действительно должны быть реализации является:

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

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

+0

Это немного странно объяснить, но основная причина, почему я должен использовать мьютекс, это то, что я использую стороннюю C DLL, которая не является потокобезопасной. Это DLL, которая позволяет мне отправлять команды по сети связи, и каждый сервер, на котором я пишу, должен иметь свою собственную копию DLL. Однако они должны использовать статику где-то, потому что блокировка не помогает. Единственное временное решение, которое я могу придумать, - это сделать системную блокировку как мьютекс. В конце концов, я думаю, что их предложение иметь отдельные копии DLL не требуется. Как два процесса, все работает нормально. – Dave

+1

@ Эрик: +1 для более глубокого ответа на вопрос. Для шаблона, который вы описываете в конце, я обычно стараюсь использовать что-то вроде подхода «копирование на запись», чтобы упростить обработку отката состояния во время исключения. Он также позволяет выполнять большую часть операции * вне * мьютекса, что повышает производительность. http://en.wikipedia.org/wiki/Copy-on-write – LBushkin

3

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

Чтобы создать и использовать свои автоматические мьютексы так, как вы пожелаете, реализуйте 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 
} 
Смежные вопросы