Пятница, 22.11.2024, 08:59
Приветствую Вас Гость | RSS | PDA

Всё для студента информата

Полезная информация

Статьи IT

Всё для студента IT » Статьи » Программирование для Microsoft® .NET

Недетерминированное уничтожение в .NET Framework

Недетерминированное уничтожение
В традиционных программных средах объекты создаются и уничтожаются в точные, определенные моменты времени. В качестве примера рассмотрим класс, написанный на неуправляемом C++:

class File
{
protected:
int Handle; // Описатель файла

public:
File (char* name)
{
// Открыть файл и скопировать описатель в Handle.
}
~File ()
{
// Закрыть описатель файла.
}
}

При создании экземпляра класса вызывается конструктор этого класса:

File* pFile = new File ("Readme.txt");

А при удалении объекта вызывается его деструктор: delete pFile;

При создании объекта не в куче, а в стеке его уничтожение все еще остается детерминированным, так как деструктор класса вызывается в тот момент, когда объект
выходит за границы области видимости.

Иначе уничтожаются объекты в .NET Framework. Вспомните: вы создаете объекты, но никогда не удаляете их — за вас это делает сборщик мусора. В этом-то и проблема. Допустим, вы написали класс File на С#:

class File
{
protected IntPtr Handle = IntPtr.Zero;
public File (string name)
{
// Открыть файл и скопировать описатель в Handle.
}
~File ()
{
// Закрыть описатель файла.
}
}

После этого вы создаете экземпляр класса:

File file = new File ("Readme.txt");

Теперь задайтесь вопросом: когда будет закрыт описатель файла?

Краткий ответ заключается в том, что описатель будет закрыт, когда будет уничтожен объект. А когда уничтожается объект? Когда его уничтожит сборщик мусора. Но когда это случится? В этом-то и вопрос. Неизвестно. Вы не можете знать этого потому, что сборщик сам решает, когда начать работать. Пока он не приступит к работе, деструктор объекта не будет вызван, и объект не будет уничтожен. Это и называется недетерминированным уничтожением (nondeterministic destruction, NDD). Формально в управляемом коде нет такого понятия, как деструктор. Когда вы пишете что-то вроде деструктора на С#, в действительности компилятор переопределяет метод Finalize, который ваш класс наследует от System.Object. СП упрощает синтаксис, позволяя писать некоторое подобие деструктора. Но это только ухудшает дело, поскольку предполагается, что это деструктор, а для несведущих разработчиков деструктор означает детерминированное разрушение.

В приложениях .NET Framework не происходит детерминированного уничтожения, если только ваш код не сотворит что-либо действительно безобразное вроде:

GC.Collect ();

GC — класс из пространства имен System, он обеспечивает программный интерфейс для сборщика мусора. Collect — это статический метод, принудительно инициирующий сборку.

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

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

Описатели файлов — не единственная проблема. Возьмем, к примеру, растровые изображения. В FCL есть для работы с ними небольшой удобный класс Bitmap (в пространстве имен SystemDrawing), понимающий множество их форматов. Когда вы создаете объект Bitmap на машине Windows, он обращается к Windows GDI. Создает битовую карту GDI и помещает ее описатель в некоторое поле. А дальше?

Пока сборщик не начнет работать и не вызовет метод Finalize объекта Bitmap, битовая карта GDI. останется открытой. Большие битовые карты занимают много памяти, поэтому вполне вероятно, что спустя некоторое время с начала работы приложения оно станет при каждой попытке создать битовую карту выдавать предупреждения о возникновении исключительной ситуации из-за нехватки памяти. Вряд ли пользователям понравится программа просмотра изображений если ее придется перезапускать каждые несколько
минут.

Так как быть с недетерминированным уничтожением? Есть два правила, позволяющих избежать связанных с ним осложнений. Первое адресовано тем программистам, которые не пишут, а используют классы, инкапсулирующие описатели файлов и другие неуправляемые ресурсы. В большинстве таких классов применяются методы Close или Dispose, которые освобождают ресурсы, требующие детерминированного закрытия. Если вы применяете классы — оболочки неуправляемых ресурсов, вызывайте для них Close или Dispose в тот момент, когда вы завершили их использование. Допустим, в File метод Close закрывает инкапсулированный описатель файла. Тогда правильно применять класс File так:

File file = new File ("Readme.txt");
// Закончив использовать файл, закройте его.
file.Close( );

Второе правило, которое на деле представляет собой целый набор правил, адресовано тем, кто пишет классы-оболочки неуправляемых ресурсов.

• Применяйте защищенный метод Dispose (здесь и далее он будет называться «защищенный Dispose''), в котором параметром является булева переменная. Этим методом освобождайте любые неуправляемые ресурсы (такие, как описатели файлов), инкапсулированные в классе. Если параметр, переданный защищенному Dispose, имеет значение true, вызывайте также Close или Dispose (открытый Dispose наследуется от IDisposable) для любых членов класса (полей), которые являются оболочками неуправляемых ресурсов.

• Реализуйте интерфейс {Disposable .NET Framework. Он содержит единственный метод Dispose без параметров. Реализуйте эту версию Dispose («открытый Dispose») с обращением к GC.SuppressFinalize для предотвращения вызова сборщиком мусора метода Finalize. После этого вызовите защищенный Dispose и передайте ему значение true.

• Переопределите Finalize. Метод Finalize вызывается сборщиком мусора, когда объект «завершен», т. е. уничтожен. В Finalize вызовите защищенный Dispose и передайте ему false. Параметр false очень важен, так как предотвращает попытку защищенного Dispose вызвать Close или открытый Dispose для любых инкапсулированных членов класса, которые могут быть уже завершены, если идет сборка мусора.

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

Вот правильный способ использования класса File с учетом изложенных принципов:

class File : IDisposable
{
protected IntPtr Handle = IntPtr.Zero;
public File (string name)
{
// Открыть файл и скопировать его описатель в Handle.
}
~File ()
{
Dispose (false);
}
public void Dispose ()
{
GC.SuppressFinalize (this);
Dispose (true);
}
protected virtual void Dispose (bool disposing)
{
// Закрыть описатель файла.
if (disposing) {
// Если в классе есть члены - оболочки
// неуправляемых ресурсов, вызовите
// здесь для них Close или Dispose.
}
}
public void Close ()
{
Dispose ();
}
}

Заметьте: теперь «деструктор» (на самом деле метод Finalize) вызывает защищенный Dispose с параметром false, а открытый Dispose вызывает защищенный Dispose и передает ему значение true. Вызов GCSuppressFinalize позволяет не только оптимизировать производительность, но и избежать повторного закрытия описателя. Поскольку' объект уже закрыл описатель файла, сборщику мусора не надо вызывать метод Finalize. Если Close или Dispose не вызываются, важно переопределить метод Finalize для корректного освобождения ресурсов.

Похожие статьи:

Не нашли то, что Вам нужно?.. Найдите ответ на форуме!
Категория: Программирование для Microsoft® .NET | Добавил: Akron (14.02.2012)
Просмотров: 566 | Теги: .NET
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Сообщество
Помощь
Форма входа
Поиск

Студенческий помощник по информатике © 2024
При цитировании материалов данного сайта, обязательна ссылка на источник: ITstudents.ru



>