Точка Dispose равна для освобождения неуправляемых ресурсов. Это нужно сделать в какой-то момент, иначе они никогда не будут очищены. Сборщик мусора не знает , как вызвать DeleteHandle()
для переменной типа IntPtr
, он не знает , или нет, ему нужно вызвать DeleteHandle()
.
Примечание : Что такое неуправляемый ресурс ? Если вы нашли его в Microsoft .NET Framework: он управляется. Если вы сами ковырялись в MSDN, это неуправляемо. Все, что вы использовали с помощью вызовов P / Invoke, чтобы выйти из приятного удобного мира всего, что доступно вам в .NET Framework, неуправляемо - и теперь вы несете ответственность за его очистку.
Созданный вами объект должен предоставить некоторый метод, который может вызвать внешний мир, чтобы очистить неуправляемые ресурсы. Метод можно назвать как угодно:
public void Cleanup()
или
public void Shutdown()
Но вместо этого есть стандартизированное имя для этого метода:
public void Dispose()
Был даже создан интерфейс, IDisposable
, который имеет только один метод:
public interface IDisposable
{
void Dispose()
}
Таким образом, вы заставляете ваш объект предоставлять интерфейс IDisposable
, и таким образом вы обещаете, что написали этот единственный метод для очистки ваших неуправляемых ресурсов:
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
И все готово. За исключением того, что вы можете сделать лучше.
Что если ваш объект выделил 250 МБ System.Drawing.Bitmap (т. Е. Управляемый .NET класс Bitmap) в качестве своего рода буфера кадров? Конечно, это управляемый объект .NET, и сборщик мусора освободит его. Но вы действительно хотите оставить 250 МБ памяти, просто сидя там - ожидая, когда сборщик мусора в конечном итоге придет и освободит его? Что если есть открытое соединение с базой данных ? Конечно, мы не хотим, чтобы это соединение оставалось открытым, ожидая, пока GC завершит объект.
Если пользователь вызвал Dispose()
(то есть он больше не планирует использовать объект), почему бы не избавиться от этих расточительных растровых изображений и соединений с базой данных?
Так что теперь мы будем:
- избавиться от неуправляемых ресурсов (потому что мы должны) и
- избавиться от управляемых ресурсов (потому что мы хотим быть полезными)
Итак, давайте обновим наш метод Dispose()
, чтобы избавиться от этих управляемых объектов:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
И все хорошо, за исключением того, что вы можете сделать лучше !
Что если человек забыл , чтобы позвонить Dispose()
на ваш объект? Тогда они будут пропускать некоторые неуправляемые ресурсы!
Примечание: Они не будут пропускать управляемые ресурсы, потому что в конечном итоге сборщик мусора будет работать в фоновом потоке и освобождать память, связанную с любыми неиспользуемыми объектами. Это будет включать ваш объект и любые управляемые объекты, которые вы используете (например, Bitmap
и DbConnection
).
Если человек забыл позвонить Dispose()
, мы можем еще спасти их бекон! У нас все еще есть способ назвать его для них: когда сборщик мусора наконец-то приступает к освобождению (т.е. завершению) нашего объекта.
Примечание: Сборщик мусора в конечном итоге освободит все управляемые объекты.
Когда это происходит, он вызывает Finalize
метод на объекте. GC не знает, или
уход, о ваш Утилизируйте метод.
Это было просто имя, которое мы выбрали для
метод, который мы вызываем, когда мы хотим получить
избавиться от неуправляемых вещей.
Уничтожение нашего объекта сборщиком мусора является идеальным временем для освобождения этих надоедливых неуправляемых ресурсов. Мы делаем это путем переопределения метода Finalize()
.
Примечание: В C # вы явно не переопределяете метод Finalize()
.
Вы пишете метод, который выглядит как a C ++ деструктор , и
компилятор считает, что это ваша реализация метода Finalize()
:
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Но в этом коде есть ошибка. Видите ли, сборщик мусора работает в фоновом потоке ; Вы не знаете порядок, в котором уничтожены два объекта. Вполне возможно, что в вашем Dispose()
коде управляемого объекта, от которого вы пытаетесь избавиться (потому что вы хотели быть полезным), больше нет:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
this.frameBufferImage = null;
}
}
Так что вам нужен способ Finalize()
сообщить Dispose()
, что он не должен касаться каких-либо управляемых ресурсов (потому что их может уже не быть ), в то время как по-прежнему освобождает неуправляемые ресурсы.
Стандартный шаблон для этого состоит в том, чтобы Finalize()
и Dispose()
оба вызывали третий (!) Метод; где вы передаете логическое выражение, если вы вызываете его из Dispose()
(в отличие от Finalize()
), то есть безопасно освобождать управляемые ресурсы.
Этот внутренний метод может получить какое-то произвольное имя, например "CoreDispose" или "MyInternalDispose", но по традиции его называют Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
Но более полезное имя параметра может быть:
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too, but only if I'm being called from Dispose
//(If I'm being called from Finalize then the objects might not exist
//anymore
if (itIsSafeToAlsoFreeManagedObjects)
{
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
}
И вы измените свою реализацию метода IDisposable.Dispose()
на:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
и ваш финализатор:
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
Примечание : Если ваш объект происходит от объекта, который реализует Dispose
, не забудьте вызвать их base Метод Dispose при переопределении Dispose:
public override void Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
И все хорошо, за исключением того, что вы можете сделать лучше !
Если пользователь вызывает Dispose()
на вашем объекте, то все было очищено. Позже, когда придет сборщик мусора и вызовет Finalize, он снова вызовет Dispose
.
Мало того, что это расточительно, но если ваш объект имеет ненужные ссылки на объекты, которые вы уже удалили из последнего вызова Dispose()
, вы попытаетесь утилизировать их снова!
Вы заметите, что в моем коде я старался удалять ссылки на объекты, которые я выбрал, поэтому я не пытаюсь вызывать Dispose
для ссылки на ненужные объекты. Но это не остановило скрытую ошибку.
Когда пользователь вызывает Dispose()
: дескриптор CursorFileBitmapIconServiceHandle уничтожается. Позже, когда запустится сборщик мусора, он снова попытается уничтожить тот же дескриптор.
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
...
}
Способ, которым вы исправляете это, говорит сборщику мусора, что ему не нужно беспокоиться о завершении объекта - его ресурсы уже очищены, и больше не требуется никакой работы. Вы делаете это, вызывая GC.SuppressFinalize()
в методе Dispose()
:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}
Теперь, когда пользователь позвонил Dispose()
, мы имеем:
- освобожденные неуправляемые ресурсы
- освобожденные управляемые ресурсы
Нет смысла в ГХ запускать финализатор - обо всем позаботились.
Не могу ли я использовать Finalize для очистки неуправляемых ресурсов?
Документация для Object.Finalize
гласит:
Метод Finalize используется для выполнения операций очистки неуправляемых ресурсов, удерживаемых текущим объектом, до уничтожения объекта.
Но в документации MSDN также сказано: IDisposable.Dispose
:
Выполняет определенные приложением задачи, связанные с освобождением, освобождением или сбросом неуправляемых ресурсов.
Так что это? Какое место для меня, чтобы очистить неуправляемые ресурсы? Ответ:
Это твой выбор! Но выберите Dispose
.
Вы, конечно, можете поместить свою неуправляемую очистку в финализатор:
~MyObject()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.
}
Проблема в том, что вы понятия не имеете, когда сборщик мусора доберется до завершения вашего объекта. Ваши неуправляемые, ненужные, неиспользуемые собственные ресурсы будут работать до тех пор, пока сборщик мусора в конечном итоге не запустится. Затем он вызовет ваш метод финализатора; очистка неуправляемых ресурсов. Документация Object.Finalize указывает на это:
Точное время выполнения финализатора не определено. Чтобы обеспечить детерминированное освобождение ресурсов для экземпляров вашего класса, реализуйте метод Close или предоставьте реализацию IDisposable.Dispose
.
Это достоинство использования Dispose
для очистки неуправляемых ресурсов; Вы узнаете и контролируете, когда неуправляемые ресурсы очищаются. Их разрушение "детерминировано" .
Чтобы ответить на ваш первоначальный вопрос: почему бы не освободить память сейчас, а не тогда, когда GC решит это сделать? У меня есть программное обеспечение для распознавания лиц, которому нужно , чтобы избавиться от 530 МБ внутренних изображений , теперь , поскольку они больше не нужны. Когда мы этого не сделаем: машина остановится.
Бонусное чтение
Для всех, кому нравится стиль этого ответа (объясняющего почему , поэтому как становится очевидным), я предлагаю вам прочитать Главу 1 «Основного COM» Дона Бокса:
На 35 страницах он объясняет проблемы использования бинарных объектов и изобретает COM на ваших глазах. Как только вы поймете , почему COM, оставшиеся 300 страниц очевидны и просто детализируют реализацию Microsoft.
Я думаю, что каждый программист, который когда-либо имел дело с объектами или COM, должен, по крайней мере, прочитать первую главу. Это лучшее объяснение всего.
Дополнительное чтение бонусов
Когда все, что вы знаете, неправильно Эрик Липперт
Поэтому очень трудно действительно написать правильный финализатор,
и лучший совет, который я могу вам дать, - не пытаться .