Что произойдет, если я не вызову Dispose на объекте пера?

Что произойдет, если я не вызываю Dispose в объекте pen в этом фрагменте кода?

private void panel_Paint(object sender, PaintEventArgs e)
{
    var pen = Pen(Color.White, 1);
    //Do some drawing
}

Ответы

Ответ 1

Pen будет собираться GC в какой-то неопределенной точке в будущем, независимо от того, вызываете ли вы Dispose.

Однако любые неуправляемые ресурсы, удерживаемые пером (например, дескриптор GDI +), не будут очищены GC. GC только очищает управляемые ресурсы. Вызов Pen.Dispose позволяет гарантировать, что эти неуправляемые ресурсы будут очищены своевременно и что вы не будете пропускать ресурсы.

Теперь, если Pen имеет финализатор и что финализатор очищает неуправляемые ресурсы, тогда указанные неуправляемые ресурсы будут очищены, когда Pen будет собран мусором. Но дело в том, что:

  • Вы должны явно вызвать Dispose, чтобы освободить неуправляемые ресурсы и
  • Вам не нужно беспокоиться о детализации реализации, если есть финализатор, и он очищает неуправляемые ресурсы.

Pen реализует IDisposable. IDisposable предназначен для удаления неуправляемых ресурсов. Это шаблон в .NET.

Предыдущие комментарии по этой теме см. в этом .

Ответ 2

Несколько исправлений должны быть сделаны здесь:

Относительно ответа Фила Девани:

"... вызов Dispose позволяет выполнять детерминированную очистку и настоятельно рекомендуется."

На самом деле, вызов Dispose() не вызывает детерминированную причину сбора GC в .NET, то есть он НЕ запускает GC сразу только потому, что вы вызвали Dispose(). Это только косвенно сигнализирует GC, что объект может быть очищен во время следующего GC (для Поколения, в котором этот объект живет). Другими словами, если объект живет в Gen 1, то он не будет утилизирован до тех пор, пока не будет произведена коллекция Gen 1. Единственный способ (хотя и не единственный), который вы можете программно и детерминистически заставить GC выполнить коллекцию, - это вызвать GC.Collect(). Однако делать это не рекомендуется, поскольку GC "настраивается" во время выполнения, собирая метрики о распределении памяти во время выполнения для вашего приложения. Вызов GC.Collect() сбрасывает эти метрики и заставляет GC снова начинать "настройку".

Относительно ответа:

IDisposable предназначен для утилизации неуправляемых ресурсов. Это шаблон в .NET.

Это неполно Поскольку GC является недетерминированным, доступен шаблон Dispose (Как правильно реализовать шаблон Dispose), чтобы вы могли высвободить используемые ресурсы - управляемые или неуправляемые. Он не имеет никакого отношения к тому, какие ресурсы вы выпускаете. Необходимость реализации Финализатора связана с тем, какие ресурсы вы используете, т.е. реализуете ТОЛЬКО один, если у вас есть не финализируемые (то есть нативные) ресурсы. Может быть, вы путаете их. Кстати, вам следует избегать реализации Finalizer, используя вместо этого класс SafeHandle, который оборачивает собственные ресурсы, которые маршалируются через P/Invoke или COM Interop. Если вы в конечном итоге внедрите Финализатор, вы должны всегда реализовывать шаблон Dispose.

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

Если GC.SuppressFinalize() в конечном счете не вызывается, тогда финализатор для объекта будет вызван на следующем GC. Обратите внимание, что правильная реализация шаблона Dispose должна вызывать GC.SuppressFinalize(). Таким образом, если вы вызовите Dispose() для объекта, и он правильно реализовал шаблон, вы избежите выполнения Finalizer. Если вы не вызываете Dispose() для объекта, у которого есть финализатор, у объекта будет финализатор, выполняемый GC в следующей коллекции. Почему это плохо? Финализатор потока в CLR вплоть до .NET 4.6 включительно является однопоточным. Представьте, что произойдет, если вы увеличите нагрузку на этот поток - производительность вашего приложения зависит от того, где вы знаете.

Вызов Dispose для объекта предусматривает следующее:

  1. уменьшить нагрузку на ГХ для процесса;
  2. уменьшить нагрузку на память приложения;
  3. уменьшить вероятность возникновения OutOfMemoryException (OOM), если LOH (куча больших объектов) фрагментируется и объект находится в LOH;
  4. Держите объект вне финализируемой и f-достижимой очередей, если у него есть финализатор;
  5. Убедитесь, что ваши ресурсы (управляемые и неуправляемые) очищены.

Редактирование: Я только что заметил, что "всезнающая и всегда правильная" документация MSDN по IDisposable (крайний сарказм здесь) на самом деле говорит

Основное использование этого интерфейса освободить неуправляемые ресурсы

Как должен знать каждый, MSDN далек от правильности, никогда не упоминает и не показывает "лучшие практики", иногда предоставляет примеры, которые не компилируются и т.д. К сожалению, это задокументировано в этих словах. Однако я знаю, что они пытались сказать: в идеальном мире GC очистит все управляемые ресурсы для вас (насколько идеалистично); однако он не будет очищать неуправляемые ресурсы. Это абсолютно верно. Тем не менее, жизнь не идеальна и не является приложением. GC будет очищать только те ресурсы, у которых нет корневых ссылок. В этом и заключается проблема.

Среди 15-20 различных способов, которыми .NET может "вытекать" (или не освобождать) память, наиболее вероятным, если вы не вызовите Dispose(), может быть отказ в регистрации события /unhook/unwire/detach. обработчиках/делегатов. Если вы создаете объект, к которому подключены делегаты, и вы не вызываете Dispose() (и не отсоединяете делегатов самостоятельно), GC по-прежнему будет видеть объект как имеющий корневые ссылки - то есть делегаты. Таким образом, сборщик мусора никогда его не соберет.

@joren комментарий/вопрос ниже (мой ответ слишком длинный для комментария):

У меня есть запись в блоге о шаблоне Dispose, который я рекомендую использовать - (Как правильно реализовать шаблон Dispose). Есть моменты, когда вы должны аннулировать ссылки, и это никогда не повредит. На самом деле это делает что-то перед запуском GC - он удаляет корневую ссылку на этот объект. Позднее GC сканирует свою коллекцию корневых ссылок и собирает те, у которых нет корневых ссылок. Подумайте об этом примере, когда это хорошо: у вас есть экземпляр типа "ClassA" - пусть назовем его "X". X содержит объект типа "ClassB" - позвольте назвать это "Y". Y реализует IDisposable, таким образом, X должен сделать то же самое, чтобы избавиться от Y. Предположим, что X находится в поколении 2 или LOH, а Y находится в поколении 0 или 1. Когда Dispose() вызывается для X, и эта реализация обнуляет ссылка на Y, корневая ссылка на Y немедленно удаляется. Если GC происходит для Gen 0 или Gen 1, память/ресурсы для Y очищены, но память/ресурсы для X нет, так как X живет в Gen 2 или LOH.

Ответ 3

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

Ответ 4

Если вы действительно хотите знать, насколько плохо, когда вы не вызываете Dispose для графических объектов, вы можете использовать CLR Profiler, доступный бесплатно для загрузки здесь. В папке установки (по умолчанию C:\CLRProfiler) - это CLRProfiler.doc, который имеет хороший пример того, что происходит, когда вы не вызываете Dispose для объекта Brush. Это очень поучительно. Короткая версия состоит в том, что графические объекты занимают больший объем памяти, чем вы могли ожидать, и они могут долго зависать, если вы не вызовете для них Dispose. Когда объекты больше не используются, система, в конце концов, очистит их, но этот процесс занимает больше процессорного времени, чем если бы вы только что вызвали Dispose, когда закончили работу с объектами. Вы также можете прочитать об использовании IDisposable здесь и здесь.

Ответ 5

Общее количество используемой памяти .Net - это .Net part + все используемые "внешние" данные. Объекты ОС, открытые файлы, базы данных и сетевые подключения используют некоторые ресурсы, которые не являются чисто объектами .Net.

Графика использует ручки и другие объекты, которые на самом деле являются объектами ОС, которые "довольно дороги" для хранения. (Вы можете поменять свое перо на 1000x1000 bitmap файл). Эти объекты ОС удаляются только из памяти ОС, как только вы вызываете определенную функцию очистки. Функции Pen и Bitmap Dispose делают это для вас немедленно, когда вы их вызываете.

Если вы не вызываете Dispose, сборщик мусора придет, чтобы очистить их "где-то в будущем". (Он фактически вызовет код деструктора/завершения, который, вероятно, вызывает Dispose())

* на машине с бесконечной памятью (или более 1 ГБ) где-то в будущем может быть очень далеко в будущем. На машине, которая ничего не делает, может быть легко дольше, чем 30 минут, чтобы очистить это огромное растровое изображение или очень маленькую ручку.

Ответ 6

Он будет хранить ресурсы до тех пор, пока сборщик мусора не очистит его.

Ответ 7

Зависит от того, реализует ли он финализатор и вызывает Dispose по его методу finalize. Если это так, дескриптор будет выпущен в GC.

если нет, дескриптор останется вокруг до тех пор, пока процесс не будет завершен.

Ответ 8

С графическим материалом это может быть очень плохо.

Откройте диспетчер задач Windows. Нажмите "Выбрать столбцы" и выберите столбец "Объекты GDI".

Если вы не располагаете определенными графическими объектами, это число будет продолжать поднимать и поднимать.

В старых версиях Windows это может привести к сбою всего приложения (ограничение было 10000, насколько я помню), но не уверен в Vista/7, хотя это все равно плохо.

Ответ 9

сборщик мусора будет собирать его в любом случае, НО это имеет значение КОГДА: если вы не вызываете dispose на объект, который вы не используете, он будет жить дольше в памяти и получает повышение до более высоких поколений, что означает, что сбор его имеет более высокую стоимость.

Ответ 10

В моей голове появилась первая идея, что этот объект будет удален, как только метод завершит выполнение!, я не знаю, где я получил эту информацию!, это правильно?