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

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

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

10 ответов

Решение

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

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

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

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

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

Для предыдущих комментариев по этой теме, пожалуйста, смотрите этот ответ.

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

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

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

На самом деле, вызов Dispose() не вызывает определенную коллекцию GC в.NET, то есть он НЕ запускает GC сразу, потому что вы вызвали Dispose(). Это только косвенно сигнализирует GC, что объект может быть очищен во время следующего GC (для Поколения, в котором этот объект живет). Другими словами, если объект живет в 1-ом поколении, он не будет утилизирован до тех пор, пока не будет произведена коллекция 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() для объекта, и он правильно реализовал шаблон, вы избежите выполнения Финализатора. Если вы не вызываете 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 все равно увидит объект с корневыми ссылками - то есть делегатов. Таким образом, 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.

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

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

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

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

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

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

Это будет держать ресурсы, пока сборщик мусора не очистит это

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

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

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

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

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

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

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

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

Другие вопросы по тегам