Задача 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);
}
И вы сделали. За исключением того, что вы можете сделать лучше.
Что если ваш объект выделил 250MB 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()
метод. Вы пишете метод , который выглядит как 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
, то не забывайте вызывать их базовый метод 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()
, мы имеем:
- освобожденные неуправляемые ресурсы
- освобожденные управляемые ресурсы
В GC нет смысла запускать финализатор - обо всем позаботились.
Не могу ли я использовать 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, должен, по крайней мере, прочитать первую главу. Это лучшее объяснение чего-либо.
Дополнительное чтение бонусов
Когда все, что вы знаете, неправильно , Эрик Липперт
Поэтому действительно очень сложно написать правильный финализатор, и лучший совет, который я могу вам дать, - не пытаться .