Принцип управления ресурсами RAII в .NET
От: Ignoramus  
Дата: 29.07.06 08:23
Оценка:
Напомню, что это Resource Acquisition Is Initialization — практика оборачивания любого ресурса (память, хендлы и т.д.) непосредственно после его выделения в смарт-обертку, которая освобождает ресурс в своем деструкторе. Это exception-safe метод избежания утечки ресурсов.

Хорошо, что в .NET "память не ресурс" уже, но как быть с другими ресурсами, такими как Pen, Brush, Region и т.д. Их желательно освободить немедленно после использования, а не когда GC захочет.

Можно в конце блока вызывать явно Dispose, но это не exception-safe (до этого может возникнуть исключение). В C# есть конструкция using { } — почти то, что нужно, за тем исключением, что ею неудобно (нельзя?) пользоваться если ресурс выделяется в другой функции.

В этом собственно и вопрос: насколько exception-safe является следующий код:

void Method1()
{
  using( Pen myPen = CreateMyPen() )
  {
   ...
  } // при выходе из блока (в т.ч. по исключению) происходит вызов myPen.Dispose(), очень хорошо :)
}

Pen CreateMyPen()
{
 Pen newPen = new System.Drawing.Pen(...);

 // что если здесь исключение, или даже после return, при приведении типа возвращаемого значения?
 return newPen;
}
Re: Принцип управления ресурсами RAII в .NET
От: Красин Россия  
Дата: 29.07.06 09:44
Оценка: 31 (1)
Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions
Re[2]: Принцип управления ресурсами RAII в .NET
От: _FRED_ Россия
Дата: 31.07.06 07:40
Оценка:
Здравствуйте, Красин, Вы писали:

К>Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions


И что же неуправляемого в ресурсе Pen?
Help will always be given at Hogwarts to those who ask for it.
Re: Принцип управления ресурсами RAII в .NET
От: _FRED_ Россия
Дата: 31.07.06 08:15
Оценка: 2 (2)
Здравствуйте, Ignoramus, Вы писали:

I>В этом собственно и вопрос: насколько exception-safe является следующий код:


Имхо, ничего более удобного всё равно не придумать, поэтому и следует использовать этот спрособ.

I> // что если здесь исключение, …

В таком случае об освобождении ресурса должна позаботиться сама функция. Например
Pen CreateMyPen()
{
  Pen newPen = new System.Drawing.Pen(...);
  try {
    // что если здесь исключение, …
    //…
  } catch {
    newPen.Dispose();
    throw;
  }//try

  return newPen;
}


I> // …или даже после return, при приведении типа возвращаемого значения?

А это уже "кривой" вызов, которого быть опять-же не должно. Раз точный тип возвращаемого функцией значения не известен, то
void Method1()
{
  using(IDisposable resource = CreateMyPen() )
  {
    // или даже после return, при приведении типа возвращаемого значения?
    Pen pen = (Pen)resource; // Исключение тут не помешает освобождению ресурса
    //…
  }
}
Help will always be given at Hogwarts to those who ask for it.
Re[3]: Принцип управления ресурсами RAII в .NET
От: Красин Россия  
Дата: 31.07.06 08:42
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Здравствуйте, Красин, Вы писали:


К>>Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions


_FR>И что же неуправляемого в ресурсе Pen?


То, что он является оберткой над HPEN.
Re[4]: Принцип управления ресурсами RAII в .NET
От: _FRED_ Россия
Дата: 31.07.06 09:04
Оценка: +1
Здравствуйте, Красин, Вы писали:

К>>>Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions

_FR>>И что же неуправляемого в ресурсе Pen?
К>То, что он является оберткой над HPEN.

Верно, он сам по себе уже управляемая обёртка => зачем мудрить с Cer
Help will always be given at Hogwarts to those who ask for it.
Re[5]: Принцип управления ресурсами RAII в .NET
От: Ignoramus  
Дата: 31.07.06 10:50
Оценка: +1
Здравствуйте, _FRED_, Вы писали:

_FR>Здравствуйте, Красин, Вы писали:


К>>>>Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions

_FR>>>И что же неуправляемого в ресурсе Pen?
К>>То, что он является оберткой над HPEN.

_FR>Верно, он сам по себе уже управляемая обёртка => зачем мудрить с Cer


Возможно, я неудачно выразился. Дело не в Pen конечно, но уже хуже если это Image. Речь идет о детерменированном во времени вызове Dispose, а если посмотреть еще более общо — об автоматическом выполнении некоторых действий при выходе из блока — т.н. scope guard.

В С++ я повсюду использовал автоматические действия в деструкторах тем же boost::shared_ptr, а в С# проблемы. Кругом ставить try/catch — слишком криво, если учесть что С# = (С++)++. И потом, есть ситуации когда и это не гарантирует.

Должна быть в С# техника детерминированного вызова деструктора (метода?) при выходе из блока. Просто я ее не знаю .
Re[2]: Принцип управления ресурсами RAII в .NET
От: Ignoramus  
Дата: 31.07.06 10:56
Оценка: -1
Здравствуйте, _FRED_, Вы писали:

_FR>Здравствуйте, Ignoramus, Вы писали:


_FR>В таком случае об освобождении ресурса должна позаботиться сама функция. Например

_FR>
_FR>Pen CreateMyPen()
_FR>{
_FR>  Pen newPen = new System.Drawing.Pen(...);
_FR>  try {
_FR>    // что если здесь исключение, …
_FR>    //…
_FR>  } catch {
_FR>    newPen.Dispose();
_FR>    throw;
_FR>  }//try

_FR>  return newPen;
_FR>}
_FR>


Имхо такая практика — шаг назад даже по отношению к unmanaged C++, VB6, да к любому reference counting. Очень на старое доброе С смахивает, если вместо try/catch поставить проверку кода ошибки.

_FR>
I>> // …или даже после return, при приведении типа возвращаемого значения?
_FR>

_FR>А это уже "кривой" вызов, которого быть опять-же не должно. Раз точный тип возвращаемого функцией значения не известен, то
_FR>[c#]

Почему же кривой? Я просто привел пример как исключение может возникнуть при передаче результата через return — при преобразовании типа. Еще может быть при копировании объекта, если это value-тип. Или при каком-нибудь переопределенном implicit преобразовании после return. Не суть важно, факт в том, что есть промежуток времени, в течение которого объект не "защищен" и в случае исключения Dispose вызван не будет.
Re[2]: Принцип управления ресурсами RAII в .NET
От: Ignoramus  
Дата: 31.07.06 11:00
Оценка:
Здравствуйте, Красин, Вы писали:

К>Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions


Это интересно, но а) как-то немного непонятно написано и б) похоже это не совсем то, о чем я спрашивал. Видимо, тут речь идет о гарантиях exception safety — т.е. инварианты, транзакции, откаты в случае сбоя и т.п.

Впрочем, эта тема меня тоже интересует. Столкнулся с тем, что для обеспечения инварианта (т.е. непротиворечивого внутреннего состояния) объекта приходится создавать полную копию объекта прежде чем его изменить. Это дорого. А как народ делает?
Re[6]: Принцип управления ресурсами RAII в .NET
От: Константин Л. Россия  
Дата: 31.07.06 11:28
Оценка:
Здравствуйте, Ignoramus, Вы писали:

I>Здравствуйте, _FRED_, Вы писали:


_FR>>Здравствуйте, Красин, Вы писали:


К>>>>>Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions

_FR>>>>И что же неуправляемого в ресурсе Pen?
К>>>То, что он является оберткой над HPEN.

_FR>>Верно, он сам по себе уже управляемая обёртка => зачем мудрить с Cer


I>Возможно, я неудачно выразился. Дело не в Pen конечно, но уже хуже если это Image. Речь идет о детерменированном во времени вызове Dispose, а если посмотреть еще более общо — об автоматическом выполнении некоторых действий при выходе из блока — т.н. scope guard.


I>В С++ я повсюду использовал автоматические действия в деструкторах тем же boost::shared_ptr, а в С# проблемы. Кругом ставить try/catch — слишком криво, если учесть что С# = (С++)++. И потом, есть ситуации когда и это не гарантирует.


I>Должна быть в С# техника детерминированного вызова деструктора (метода?) при выходе из блока. Просто я ее не знаю .


Конструкция using, которая разворачивается примерно так:


//исходный код

using(A obj = new A())
{
   obj.DoSome();
}

//после разворачивания

try
{   
   obj.DoSome();
}
finally
{
   obj.Dispose(true);
}
Estuve en Granada y me acorde' de ti
Re[6]: Принцип управления ресурсами RAII в .NET
От: Константин Л. Россия  
Дата: 31.07.06 11:31
Оценка:
Здравствуйте, Ignoramus, Вы писали:

I>Здравствуйте, _FRED_, Вы писали:


_FR>>Здравствуйте, Красин, Вы писали:


К>>>>>Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions

_FR>>>>И что же неуправляемого в ресурсе Pen?
К>>>То, что он является оберткой над HPEN.

_FR>>Верно, он сам по себе уже управляемая обёртка => зачем мудрить с Cer


I>Возможно, я неудачно выразился. Дело не в Pen конечно, но уже хуже если это Image. Речь идет о детерменированном во времени вызове Dispose, а если посмотреть еще более общо — об автоматическом выполнении некоторых действий при выходе из блока — т.н. scope guard.


I>В С++ я повсюду использовал автоматические действия в деструкторах тем же boost::shared_ptr, а в С# проблемы. Кругом ставить try/catch — слишком криво, если учесть что С# = (С++)++. И потом, есть ситуации когда и это не гарантирует.


I>Должна быть в С# техника детерминированного вызова деструктора (метода?) при выходе из блока. Просто я ее не знаю .


Кроме того, финализаторы никто не отменял. То, что они вызовутся, почти 100 гарантия. Просто никто не гарантирует, что они вызовутся по выходу из скопа.
Estuve en Granada y me acorde' de ti
Re[6]: Принцип управления ресурсами RAII в .NET
От: _FRED_ Россия
Дата: 31.07.06 12:37
Оценка:
Здравствуйте, Ignoramus, Вы писали:

К>>>>>Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions

_FR>>>>И что же неуправляемого в ресурсе Pen?
К>>>То, что он является оберткой над HPEN.
_FR>>Верно, он сам по себе уже управляемая обёртка => зачем мудрить с Cer
I>Возможно, я неудачно выразился. Дело не в Pen конечно, но уже хуже если это Image. Речь идет о детерменированном во времени вызове Dispose, а если посмотреть еще более общо — об автоматическом выполнении некоторых действий при выходе из блока — т.н. scope guard.

Принципиально то, что Pen уже сам по себе является управляемой обёрткой и городить лишний огород с ним незачем. Работа с неуправляемыми ресурсами — совсем другая песня по сравнению с управляемыми. В корневом сообщении, я так понял, вопрос именно про управляемые — IDisposable — ресурсы.

I>В С++ я повсюду использовал автоматические действия в деструкторах тем же boost::shared_ptr, а в С# проблемы. Кругом ставить try/catch — слишком криво, если учесть что С# = (С++)++.


Не try/catch, а try/finally, что уже ни капли не криво.

I>И потом, есть ситуации когда и это не гарантирует.


Кто не гарантирует? что не гарантирует

I>Должна быть в С# техника детерминированного вызова деструктора (метода?) при выходе из блока.


Да, и называется using.

Раз ты такой большой специалист "В С++" покажи-ка примерчик на этом языке, реализующий то, о чём было спрошено в корневом сообщении — как гарантировать освобождение ресурса "внутри" метода , когда ресурс уже захвачен, но ещё не отдан без "кривых" "try/catch"
Help will always be given at Hogwarts to those who ask for it.
Re[7]: Принцип управления ресурсами RAII в .NET
От: _FRED_ Россия
Дата: 31.07.06 12:48
Оценка:
Здравствуйте, _FRED_, Вы писали:

I>>В С++ я повсюду использовал автоматические действия в деструкторах тем же boost::shared_ptr, а в С# проблемы. Кругом ставить try/catch — слишком криво,

_FR>Не try/catch, а try/finally, что уже ни капли не криво.

Нет, всё-тки try/catch но сути это не меняет, так как с исключением ничего не делается и оно не пропадает.

I>>если учесть что С# = (С++)++.


А что эта фраза значит? Вкупе с упоминанием "try/catch — слишком криво"
Help will always be given at Hogwarts to those who ask for it.
Re[3]: Принцип управления ресурсами RAII в .NET
От: mrozov  
Дата: 31.07.06 12:56
Оценка:
Здравствуйте, Ignoramus, Вы писали:

I>Здравствуйте, Красин, Вы писали:


К>>Для написания устойчивых к сбоям оберток над неуправляемыми ресурсами, во втором дотнете появились Constrained Execution Regions


I>Это интересно, но а) как-то немного непонятно написано и б) похоже это не совсем то, о чем я спрашивал. Видимо, тут речь идет о гарантиях exception safety — т.е. инварианты, транзакции, откаты в случае сбоя и т.п.


I>Впрочем, эта тема меня тоже интересует. Столкнулся с тем, что для обеспечения инварианта (т.е. непротиворечивого внутреннего состояния) объекта приходится создавать полную копию объекта прежде чем его изменить. Это дорого. А как народ делает?


Народ делает так.
1. В начале работы создает копию и работает с ней. Если нужно — подменяет исходное состояние (можно и наоборот).
2. Запоминает в надежном месте каждое действие и по мере надобности — прокатывает все взад (паттерн Command рулит).
3. Тут могла бы быть твоя нобелевка
Re[7]: Принцип управления ресурсами RAII в .NET
От: Константин Л. Россия  
Дата: 31.07.06 13:04
Оценка:
Здравствуйте, _FRED_, Вы писали:

пожалуйста:


class Handle
{
    HANDLE Handle_;

    void swap( Handle& rhs )
    {
         HANDLE tmp = Handle_;
         Handle_ = rhs.Handle_;
         rhs.Handle_ = tmp;
    }   

   void close()
   {
       if( INVALID_HANDLE_VALUE != Handle_ )
       {
           ::CloseHandle(Handle_);
           Handle_ = INVALID_HANDLE_VALUE;
       }        
   }

public:

   explicit Handle( HANDLE handle )
       : Handle_(handle)
   {}

   Handle( Handle& rhs )
       : Handle_(rhs.Handle_)
   {
       rhs.Handle_ = INVALID_HANDLE_VALUE;
   }

   Handle& operator  =( Handle& rhs )
   {       
       close();       
       swap(rhs);
       return *this;
   }

   ~Handle()
    {
        close();
    }
};
Estuve en Granada y me acorde' de ti
Re[8]: Принцип управления ресурсами RAII в .NET
От: _FRED_ Россия
Дата: 31.07.06 13:17
Оценка:
Здравствуйте, Константин Л., Вы писали:

КЛ>пожалуйста:


Я так и не увидел кода метода. аналогичного CreateMyPen


КЛ>   Handle( Handle& rhs )
КЛ>       : Handle_(rhs.Handle_)
КЛ>   {
КЛ>       rhs.Handle_ = INVALID_HANDLE_VALUE;
КЛ>   }

Это слишком дубово, как на счёт подсчёта ссылок?

КЛ>   Handle& operator  =( Handle& rhs )
КЛ>   {       
КЛ>       close();       
КЛ>       swap(rhs);
КЛ>       return *this;
КЛ>   }

Во-во! А уж о том, на сколько более "простым" это делает чтение кода я просто молчу
Такой простой оператор, и надо же! побочное явление

Неужели кому-то это кажется _понятнее_ using(…)
Help will always be given at Hogwarts to those who ask for it.
Re[9]: Принцип управления ресурсами RAII в .NET
От: Константин Л. Россия  
Дата: 31.07.06 13:22
Оценка:
Здравствуйте, _FRED_, Вы писали:

[]
_FR>Это слишком дубово, как на счёт подсчёта ссылок?

это уже другая история

[]

_FR>Неужели кому-то это кажется _понятнее_ using(…)


Человек спрашивает, как жить, когда объект создается до using. Он имеет ввиду то, что в с++ с RAII контроль над объектом должен держать другой объект в рамках ctor/dtor, те на всем протяжении жизни объекта. Он интересуется, как такое разруливается в c#. Мы ему говорим — IDisposable + finalizators
Estuve en Granada y me acorde' de ti
Re[4]: Принцип управления ресурсами RAII в .NET
От: Ignoramus  
Дата: 01.08.06 13:10
Оценка:
Здравствуйте, mrozov, Вы писали:

>Народ делает так.

M>1. В начале работы создает копию и работает с ней. Если нужно — подменяет исходное состояние (можно и наоборот).

А если копия — массив на несколько мегабайт? Дороговато выходит, но это надежнее всего, согласен.

M>2. Запоминает в надежном месте каждое действие и по мере надобности — прокатывает все взад (паттерн Command рулит).


А вдруг при откате опять исключение? Как хотя бы инвариант состояния сохранить? (Кроме способа №1.)

M>3. Тут могла бы быть твоя нобелевка


Re[9]: Принцип управления ресурсами RAII в .NET
От: Ignoramus  
Дата: 01.08.06 13:22
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Во-во! А уж о том, на сколько более "простым" это делает чтение кода я просто молчу

_FR>Такой простой оператор, и надо же! побочное явление

_FR>Неужели кому-то это кажется _понятнее_ using(…)


Тихо, тихо. Тут никто не собирается начинать флейм что понятнее C# vs C++.

Например обертка Pen могла бы быть такой (в тривиальном варианте):

class MyPen
{
private:
  HPEN m_handle;

public:
  MyPen()
  {
    m_handle = ::CreatePen(...);
  }
  ~MyPen()
  {
    if( m_handle != 0 )
    {
      ::DeleteObject( m_handle );
    }
  }
};


Таким образом использование выглядит так:

MyPen CreateMyPen()
{
  return new MyPen();
}

void SomeFunc()
{
  MyPen myPen = CreateMyPen();
}



Таким образом, если исключение возникнет в любом месте, ресурс m_handle будет гарантированно освобожден.

Конструкция using предусматривает аналогиченое поведение (только вместо деструктора вызывается Dispose) только внутри себя, как я понимаю, после того, как произошло присвоение в скобках. Если исключение возникнет внутри CreateMyPen или при выполнении оператора присваивания, Dispose будет вызван, но не уже детерминированно, а когда приспичит GC.


А насчет (С++)++ == С# это же, АФАИК, как название языка появилось, типа следующий шаг после С++ .
Re[10]: Принцип управления ресурсами RAII в .NET
От: Ignoramus  
Дата: 01.08.06 13:25
Оценка:
Здравствуйте, Ignoramus, Вы писали:

Блин, уже привык к C# и ошибся в примере

Правильно так:

MyPen CreateMyPen()
{
  MyPen myPen
  return myPen;
}


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

И еще нужно в классе MyPen определить конструктор копирования, примерно как у Константина Л, чтобы хендл два раза не освободился
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.