Architecture Net

       

Неуправляемые ресурсы и освобождение ранее выделенной области памяти


Предположим, что некоторый объект, с помощью которого был открыт файл, программе больше не нужен и помечен для сборки мусора. В конечном счете будет вызван деструктор объекта, при выполнении которого файл может быть закрыт. Но, как мы уже обсуждали, сборка мусора — процесс недетерминированный (во времени, во всяком случае), и файл может оставаться открытым неопределенно долго. Более эффективным было бы иметь в клиентской программе детерминированный механизм освобождения ресурсов объекта, который стал ненужным. Каркас .NET Framework рекомендует для этой цели использовать интерфейс IDisposable.

public _gc _interface IDisposable
// сборщик мусора - интерфейс IDisposable
{
void Dispose();
};

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

Отметим, что недопустимо вызывать деструктор объекта после вызова метода Dispose (Освободить ранее выделенную область памяти), потому что это приведет к повторной очистке. Объект может быть удален из очереди сборки мусора с помощью GC: : SuppressFinalize. Кроме того, полезно реализовать в классе булев флажок, назвав его, например disposeCalled. Если Dispose (Освободить ранее выделенную область памяти) вызывается дважды, проверка этого флажка предотвратит повторное выполнение очистки.


Метод Dispose ( Освободить ранее выделенную область памяти) должен также вызвать метод Dispose (Освободить ранее выделенную область памяти) базового класса с целью удостовериться, что все его ресурсы тоже освобождаются. Причем и этот метод должен быть написан так, чтобы не возникала исключительная ситуация при его вызове, если ресурсы уже были освобождены.

Поскольку завершение представляет собой дорогой процесс, любой объект, который больше не будет использовать ресурсы, должен вызвать статический метод GC:: SupressFinalize, передавая ему указатель this. При наличии в коде блока try/finally можно разместить вызов метода Dispose (Освободить ранее выделенную область памяти) объекта в блоке finally (наконец), чтобы удостовериться в том, что ресурсы будут освобождены.

Пример программы DisposeDemo иллюстрирует шаблон для освобождения ресурсов. Класс SimpleLog с помощью класса StreamWriter реализует запись (информации) в файл.

//SimpleLog.h
using namespace System;
// использование пространства имен Система;
using namespace System::10;
// использование пространства имен Система::10;
public _gc class SimpleLog :
public IDisposable
// класс сборщика мусора
SimpleLog: IDisposable
{
private: // частный
StreamWriter *writer; String *name;
// Строка bool disposeCalled;
// логический (булев) флажок disposeCalled public:
SimpleLog(String *fileName) : disposeCalled(false)
// (Строка *fileName): (ложь)
{
name = fileName; // имя файла
writer = new StreamWriter(fileName, false);
// устройство записи = новый StreamWriter (имя
// файла, ложь);
writer->AutoFlush = true;
// устройство записи-> Автосброс = истина;
Console::WriteLine(
String::Format("logfile {0} created", name));
// Строка::Формат ("системный журнал (0}
// создан", имя));
}
void WriteLine(String *str) // Строка
{
writer->WriteLine(str); // запись
Console::WriteLine(str);
}
void Dispose ()
{
if(disposeCalled)
// если
(disposeCalled) - если все уже сделано
return;
writer->Close ();
GC::SuppressFinalize (this);
// СБОРЩИК МУСОРА Console::WriteLine(
String::Format("logfile {0} disposed", name));
// Строка::Формат ("системный журнал {0}
// закрыт ", имя));
disposeCalled = true;
// истина - все уже сделано
}
-SimpleLog()
{
Console::WriteLine(
String::Format("logfile {0} finalized", name));
// Строка::Формат ("системный журнал (0)
// завершен", имя));
Dispose();
}
};



Класс SimpleLog поддерживает интерфейс IDisposable и таким образом реализует метод Dispose (Освободить ранее выделенную область памяти). Код, освобождающий ресурсы, просто удаляет объект streamWriter. Чтобы удостовериться в том, что для удаленного объекта также не будет вызван метод завершения, вызывается GC: : SuppressFinalize. Завершающий работу объекта деструктор просто делегирует свои функции методу Dispose (Освободить ранее выделенную область памяти). Для контроля над продолжительностью жизни объекта на консоль выводится сообщение из конструктора, из метода Dispose (Освободить ранее выделенную область памяти) и из деструктора.
Вот код тестовой программы:

//DisposeDemo.h
using namespace System;
// использование пространства имен Система;
using namespace System::Threading;
// использование пространства имен
// Система::Организация поточной обработки;
public _gc class DisposeDemo
// класс сборщика мусора DisposeDemo
{ public:
static void Main()
{
SimpleLog *log = new SimpleLog("logl.txt");
log->WriteLine("First line");
// файл регистрации-> WriteLine ("Первая строка");

Pause(); // Пауза
log->Dispose(); // Первое завершение файла регистрации
log->Dispose(); // файл регистрации - испытание -второй
// вызов Dispose
log = new SimpleLog("Iog2.txt");
log->WriteLine("Second line");
// файл регистрации-> WriteLine ("Вторая строка");

Pause (); // Пауза log = new SimpleLog(
"Iog3.txt"); // предыдущий (2-ой) файл
// регистрации освобожден
log->WriteLine("Third line");
// файл регистрации-> WriteLine ("Третья строка");

Pause (); // Пауза
log =0; // последний файл регистрации освобожден
GC::Collect();
// СБОРЩИК МУСОРА:: Собрать ( );
Thread::Sleep(100);
// Поток:: Бездействие (100);
}
private: // частный
static void Pause)) // Пауза
{
Console::Write("Press enter to continue");
// Запись:: ("Нажмите ввод для продолжения");
String *str = Console::ReadLine(); // Строка
}
};



Указателю log ( файл регистрации) на объект SimpleLog по очереди присваиваются три различных указателя на экземпляр объекта. Первый раз ранее выделенная область памяти освобождается должным образом. Второй раз указателю присваивается указатель на третий объект. Это происходит до освобождения ранее выделенной области памяти для второго объекта. В результате второй объект становится мусором. Используя метод Pause (Пауза) для приостановки выполнения этого консольного приложения, мы можем исследовать состояние файлов logl. txt, Iog2 . txt и Iog3. txt в разные моменты выполнения программы.

Выполнение программы приводит к следующему выводу:

logfile logl.txt created First line
Press enter to continue logfile logl.txt
disposed logfile Iog2.txt created Second line
Press enter to continue logfile Iog3.txt created Third line
Press enter to continue logfile Iog3.txt
finalized logfile Iog3.txt
disposed logfile Iog2.txt
finalized logfile Iog2.txt disposed

Перевод такой:

системный журнал logl.txt создан
Первая строка
Нажмите ввод для продолжения
системный журнал logl.txt завершен
системный журнал Iog2.txt создан
Вторая строка
Нажмите ввод для продолжения
системный журнал Iog3.txt создан
Третья строка
Нажмите ввод для продолжения
системный журнал Iog3.txt завершен
системный журнал Iog3.txt освобожден
системный журнал Iog2.txt завершен
системный журнал Iog2.txt освобожден

После первой паузы файл logl. txt уже создан, и мы можем просмотреть его содержимое с помощью стандартной системной программы Блокнот (Notepad). Если попробовать удалить файл, получим нарушение процедуры совместного использования (общих ресурсов), т.е. нарушение условий коллективного доступа (ошибка совместного доступа, т.к. файл уже открыт другим приложением), как показано на рис. 8.2.

После второй паузы ранее выделенная для logl. txt область памяти будет освобождена и его можно будет удалить. Файл Iog2.txt к этому моменту уже был создан и открыт. А до третьей паузы будет создан Iog3.txt. Но объектной ссылке на 1од2 . txt было присвоено новое значение, поэтому теперь клиентская программа не может освободить область памяти, ранее выделенную для второго объекта. Если бы Dispose (Освободить ранее выделенную область памяти) был единственным механизмом для удаления второго объекта, нам бы не повезло. К счастью, в классе SimpleObject реализован деструктор, так что при следующей сборке мусора удастся избавиться от второго объекта должным образом. Можно увидеть результат завершения, выполнив программу до конца. Второй объект действительно завершается и ранее выделенная для него область памяти освобождается. Фактически при завершении работы прикладной области деструкторы вызываются для всех не уничтоженных объектов, даже для объектов, которые все еще являются доступными.






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

В нашем коде мы явно делаем третий объект недоступным, присваивая указателю пустой указатель (log = null), и принудительно устраиваем сборку мусора, вызывая GC: : Collect (СБОРЩИК МУСОРА::Собрать). Потом приложение на некоторое время переходит в режим . ожидания, чтобы сборщик мусора выполнил свою работу до конца перед завершением прикладной области. Наша тестовая программа несколько искусственна, так как сборка мусора недетерминирована. Сборщик мусора вызывается автоматически при выходе из программы и завершении работы прикладной области. Однако при этом системные объекты, такие как Console (Консоль), также закрываются. Так как нельзя полагаться на порядок завершения, можно получить исключение при вызове WriteLine внутри деструктора. Явный вызов GC: :Collect (СБОРЩИК МУСОРА::Собрать) вызывает принудительную сборку мусора в то время, когда системные объекты все еще открыты. Если мы опустим последние три строки из метода Мат (Главный), то можем получить идентичный вывод, но можем также получить и исключение.

Дополнительное имя для Dispose (Освободить ранее выделенную область памяти)

Стандартное имя метода, производящего очистку— Dispose (Освободить ранее выделенную область памяти). Соглашение состоит в том, что, как только ранее выделенная для объекта область памяти освобождается, такой объект завершает свою работу. Однако в некоторых случаях, например для файла, тот же самый экземпляр объекта мог бы использоваться повторно. Файл может быть открыт, закрыт, а затем снова открыт. При этом стандартное соглашение об именовании гласит, что метод для очистки должен называться Close (Закрыть). В других случаях может использоваться другое подходящее имя.

Чтобы вполне естественно, если бы в нашем классе SimpleLog имелся метод Open (Открыть). В этом случае метод для очистки логично было бы назвать Close (Закрыть). Простоты ради, мы не реализовали метод Open (Открыть), и поэтому придерживались имени Dispose (Освободить ранее выделенную область памяти).

CompEbook.ru Железо, дизайн, обучение и другие


Содержание раздела