Чтение онлайн

ЖАНРЫ

ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание

Троелсен Эндрю

Шрифт:

Создание типов, предусматривающих освобождение ресурсов и финализацию

К этому моменту мы с вами обсудили два различных подхода в построении классов, способных освобождать свои внутренние неуправляемые ресурсы. С одной стороны, можно переопределить System.Object.Finalize, тогда вы будете уверены в том, что объект непременно освободит ресурсы при сборке мусора, без какого бы то ни было вмешательства пользователя. С другой стороны, можно реализовать IDisposable, что обеспечит пользователю возможность освободить ресурсы после завершения работы с объектом. Однако, если вызывающая сторона "забудет" вызвать Dispose, неуправляемые ресурсы смогут оставаться в памяти неопределенно

долгое время.

Вы можете догадываться, что есть возможность комбинировать оба эти подхода в одном определении класса. Такая комбинации позволит использовать преимущества обеих моделей. Если пользователь объекта не забудет вызвать Dispose, то с помощью вызова GC.SuppressFinalize вы можете информировать сборщик мусора о том. что процесс финализации следует отменить. Еcли пользователь объекта забудет вызвать Dispose, то объект, в конечном счете, подвергнется процедуре финализации при сборке мусора. Так или иначе, внутренние неуправляемые ресурсы объекта будут освобождены. Ниже предлагается очередной вариант MyResourceWrapper, в котором теперь предусмотрены и финализация, и освобождение ресурсов.

// Сложный контейнер ресурсов.

public class MyResourceWrapper: IDisposable {

 // Сборщик мусора вызывает этот метод в том случае, когда

 // пользователь объекта забывает вызвать Dispose.

 ~MyResourceWrapper {

// Освобождение внутренних неуправляемых ресурсов.

// НЕ следует вызывать Dispose для управляемых объектов.

 }

 // Пользователь объекта вызывает этот метод для того, чтобы

 // как можно быстрее освободить ресурсы.

 public void Dispose {

// Освобождение неуправляемых ресурсов.

// Вызов Dispose для содержащихся объектов,

// предусматривающих освобождение ресурсов.

// Если пользователь вызвал Dispose, то финализация не нужна.

GC.SuppressFinalize(this);

 }

}

Обратите внимание на то, что в метод Dispose здесь добавлен вызов GC.SuppressFinalize, информирующий среду CLR о том, что теперь при сборке мусора не требуется вызывать деструктор, поскольку неуправляемые ресурсы уже освобождены с помощью программной логики Dispose.

Формализованный шаблон освобождения ресурсов

Текущая реализация MyResourceWrapper работает вполне приемлемо, но некоторые недостатки она все же имеет. Во-первых, каждому из методов Finalize и Dispose приходится очищать одни и те же неуправляемые ресурсы. Это, конечно, ведет к дублированию программного кода, что усложняет задачу его поддержки. Лучше всего определить приватную вспомогательную функцию, которая вызывалась бы каждым из двух этих методов.

Далее, следует убедиться в том, что метод Finalize не пытается освободить управляемые объекты, которые должны обрабатываться методом Dispose. Наконец, желательно гарантировать, что пользователь объекта сможет многократно вызывать метод Disposed без появления сообщений об ошибке. В настоящий момент наш метод Dispose таких

гарантий не дает.

Для решения указанных проблемы Microsoft определила формализованный шаблон для освобождения ресурсов, устанавливающий баланс между устойчивостью к ошибкам, производительностью и простотой поддержки. Вот окончательная (и аннотированная) версия MyResourceWrapper, для которой используется этот "официальный" шаблон.

public class MyResourceWrapper: IDisposable {

 // Используется для того, чтобы выяснить,

 // вызывался ли метод Dispose.

 private bool disposed = false;

 public void Dispose {

// Вызов нашего вспомогательного метода.

// Значение "true" указывает на то, что

// очистку инициировал пользователь объекта.

CleanUp(true);

// Запрет финализации.

GC.SuppressFinalize(this);

 }

 private void CleanUp(bool disposing) {

// Убедимся, что ресурсы еще не освобождены.

if (!this.disposed) {

// Если disposing равно true, освободить

// все управляемые ресурсы.

if (disposing) {

// Освобождение управляемых ресурсов.

}

// Освобождение неуправляемых ресурсов.

}

disposed = true;

 }

 ~MyResourceWrapper {

// Вызов нашего вспомогательного метода.

// Значение "false" указывает на то, что

// очистку инициировал сборщик мусора.

CleanDp(false);

 }

}

Обратите внимание на то, что теперь MyResourceWrapper определяет приватный вспомогательный метод, с именем Cleanup. Если для его аргумента указано true (истина), это значит, что сборку мусора инициировал пользователь объекта. И тогда мы должны освободить и управляемые, и неуправляемые ресурсы. Но если "уборка" инициирована сборщиком мусора, то при вызове CleanUp следует указать false (ложь), чтобы внутренние объекты не освобождались (поскольку мы не можем гарантировать, что они все еще находятся в памяти). Наконец, перед выходом из CleanUp член-переменная disposed логического типа устанавливается равной true, чтобы Dispose можно было вызывать многократно без появления сообщений об ошибках.

Исходный код. Проект FinalizableDisposableClass размещен в подкаталоге, соответствующем главе 5.

На этом наше обсуждение того, как среда CLR управляет объектами с помощью сборки мусора, завершается. Остались нерассмотренными еще ряд вопросов, связанных с процессом сборки мусора (это, например, слабые ссылки и восстановление объектов), но вы теперь имеете достаточно знаний для того, чтобы продолжить исследование данной темы самостоятельно в удобное для вас время.

Поделиться с друзьями: