Pull to refresh

Comments 77

Насколько я понял вот отсюда msdn.microsoft.com/ru-ru/library/b1yfkh5e.aspx даже при использовании IDisposable, следует реализовать и финализатор.

«Обратите внимание, что даже при наличии явного управления с помощью метода Dispose следует предоставить и неявное освобождение ресурсов с использованием метода Finalize. Метод Finalize предоставляет дополнительный способ предотвращения потери ресурсов в случае, если программист забыл вызывать Dispose.»
UFO just landed and posted this here
Всё просто. Dispose — это явное указание на то, что нужно освободить неуправляемые ресурсы прямо сейчас. Если этого указания не было, то его должен дать финализатор, будучи вызванным GC.
Скажем так, шаблон реализации IDisposable просто уберегает неуправляемые ресурсы от того, что их забыли освободить. Кстати в более поздних советах от Майкрософта натыкался на то, что лучше избегать классов владеющих управляемыми и неуправляемыми ресурсами одновременно. Впрочем ценность данного совета сомнительна.
Если у вас есть неуправляемые ресурсы, то да, обязательно.
UFO just landed and posted this here
А если кто-то забыл вызвать Dispose, что дальше? Ресурс повиснет?
Как я знаю, CLR вызывает Dispose, перед уничтожением объекта, вне зависимости от финализатора.
Вообще его рекомендуют и в финалайзере вызывать, если мне память не изменяет.
Сейчас посмотрел, оказывается не вызывает, и если используются хэндлы, то вызывать надо будет вручную. К счастью у всех BCL классов есть финализатор.
UFO just landed and posted this here
UFO just landed and posted this here
Про IDisposable/Dispose Вы сказали верно. А про деструктор (финализатор) — не совсем. Он будет вызван при сборке мусора, но когда это произойдёт — неизвестно. Аналогично, при возникновении исключения просто так деструктор вызван не будет (если только это не CriticalFinalizerObject, с ним чуть сложнее). И в этой неопределённости момента вызова финализатора есть проблема. Например, неосвобождение открытого файла может привести к проблемам при повторном его же открытии.
Как вариант ловить утечки. Хотя для этого есть более удобные и эффективные инструменты.
Или для статистики (в целом первый случай грубо говоря можно назвать статистикой).
~ClassName назвали финалайзером в первой версии C#, и деструктором начиная со второй. Вот и вся причина путаницы в документации.
может быть наоборот? потому что сейчас ~ClassName это финализатор, но не деструктор
в статье как раз и написано о том, почему деструктор в C# это не совсем деструктор и почему вышла путаница с названиями
В статье описано ваше личное мнение, а не почему есть путаница.

Есть язык C# и есть CLI. На C# можно писать программы работающие не под CLI, а под CLI запускать программы написанные не только на C#.

Есть неудобство связанное с тем что в документации к CLI и C# одно и то же называется двумя разными терминами и не более того.
Так сейчас 4-я версия, но там еще 3.5 было — так что по очередности изменений в версиях все совпадает. <joke/>
Из практики — используйте IDisposable и забудьте про Finalize навсегда. Мало того что для сборки объекта с этим методом нужно 2 сборки мусора вместо одной, так еще и будете периодически ловить очень странные утечки ресурсов в коде. Ловить будете очень долго — гарантирую.
Из практики — недавно поймали именно утечку из финализатора в классе WritableBitmap в WPF. Искали около 2х месяцев с собаками.
Почему следует забыть про finalizer? Он и IDisposable — две разные истории. Если есть возможность определить момент, когда требуется освобождать ресурс, то обязательно надо использовать using/Dispose. Но это не отменяет того, что надо писать финализатор. Он вызовется если не при Collect'е (порой слишком поздно), то при выгрузке домена/завершении приложения и т.д. В общем, я не вижу причин НЕ писать финализатор. Просто полагаться только на него, разумеется, нельзя.
Ну и что — в коде без ошибок — Dispose вызывается ВСЕГДА кроме краша приложения, но при краше и финализатор не вызовется. С другой стороны финализатор иногда скрывает ошибки программирования — когда Dispose корректно не вызван. Особенно жестко это проявляется в асинхронных и многопоточных приложениях.
Второй аргумент интересен. А первый аргумент просто несерьёзен. Покажите мне хоть одного программиста пишущего «код без ошибок» или хотя бы такой код.
Хм а они связаны :) первый и второй аргумент.
Дело в том что не надо маскировать свои ошибки — если ошибка есть — пусть лучше ударит она по голове вам на девелоперской машине под отладкой, а не пользователю в продакшене.
Кстати, а Вы могли бы всё-таки привести этот конкретный пример скрытой ошибки программирования, особенно в многопоточном приложении, интересно же? Хотя бы в общих словах.
Нипишите вот такой код в любимой среде разработки

for(var i =0; i<10000; i++)
{
new Task( () => {new WriteableBitmap();}).Start();
Console.WriteLine(i);
}

Подобавляейте Thread.Sleep() внутрь цикла. Результаты уверен вас немного удивят :)

Я не буду придираться к тому, что пример не компилируется, а я не силён в Windows.Presentation, чтобы сходу исправить его. Скажите, пожалуйста, что я должен увидеть? И на что именно влияет Thread.Sleep() в этом примере.
Числа будут разные от раза к разу. Без Sleep получите OutOfMemory при определенном Sleep в зависимости от вашей машины пример начнет полностью работать.

Пространство имен: System.Windows.Media.Imaging
Сборка: PresentationCore (в PresentationCore.dll)
public sealed class WriteableBitmap: BitmapSource

using System.Threading.Tasks;
using System.Windows.Media.Imaging;

namespace My{
class MyClass
{
public static void Main(string[] argv)
{

for(var i =0; i<10000; i++)
{
new Task( () => {new WriteableBitmap();}).Start();
Console.WriteLine(i);
}

}
}
}

Пример у меня так и не скомпилится, т.к. публичный конструктор по умолчанию отсутствует у WriteableBitmap. Ссылку на нужную сборку + нэймспейс я как-то разобрался сам вставить.

> Числа будут разные от раза к разу.

Если уж Вы воспользовались классом Task, чтобы получить параллелизм, то вряд ли кто-то гарантирует Вам детерминированный вывод в консольку. Потоки всё-таки конкурируют.

> Без Sleep получите OutOfMemory при определенном Sleep в зависимости от вашей машины пример начнет полностью работать.

Почему это? Если памяти не будет хватать, будет вызван Collect. В Вашем примере ссылки на созданные объекты нигде не сохраняются, так что они будут засчитаны как недостижимые. Их finalizer'ы будут благополучно исполнены. OutOfMemoryException возникать нет причин, имхо.
UFO just landed and posted this here
Ок, но я не утверждал, что таски являются потоками. Просто думал, что за Тасками скрываются потоки. Как иначе обеспечить параллелизм я, к сожалению, не знаю.
UFO just landed and posted this here
Shame on me. Ну не читал я про это пока… (
Dispose вызывается только тогда, когда его вызвали явно (obj.Dispose()) или неявно (using(obj)).
Кстати, в WPF нет поддержки IDisposable, поэтому неупрявляемые ресурсы кроме как в финализаторе и не очистишь.
А если где-то Dispose забыли вызвать?
Если вы считаете, что задача «где вызвать Dispose» тривиальна, то вспомните, что сборка мусора была создана именно потому, что эта задача оказалось сложной для delete.
Правильный способ для IDisposable выглядит как:
определить общий метод освобождения неуправляемых ресурсов, затем вызвать его из Dispose и из финализатора.
Чтобы предотвратить повторное освобождение уже освобожденных ресурсов и тот факт, что объект с финализатором переживет ближайшую сборку мусора, в Dispose надо вызвать GC.SupressFinalize
UFO just landed and posted this here
Не забывайте — вы можете банально не успеть вызвать финализатор и поиметь утечку ресурсов.
UFO just landed and posted this here
Ну вот так — не хватило тактов процессора на сборку мусора. Причем например на вашей машине может хватать, а вот на машинке по слабее нет. Если же финализатора нет — то вы увидите баг и на своей машине.
UFO just landed and posted this here
Да вобщем — то что вы написали это практически цитата — толи из Рихтера толи с МСДН. К сожалению такое руководство иногда приводит к сложно-отлавливаемым ошибкам. Вот и все. :)
UFO just landed and posted this here
Утечка ресурсов из-за того что объект создавался много раз и не успевал освобождаться.
UFO just landed and posted this here
UFO just landed and posted this here
Вы какие-то странности рассказываете. На период работы Collect'а сборщиком мусора, все потоки приложения суспендятся; остаётся работать только поток, вызывающий финализаторы. У него нет шансов быть вызванным. Максимум, Рихтер писал, могу быть проблемы при выгрузке домена/завершении приложения: там на всё время очистки памяти выделен лимит времени, у каждого отдельного — тоже лимит времени. Теоретически, они могут и не успеть Но повторюсь, это при выгрузке домена, а не при нормальной работе программы.
Если же Вы имели в виду, что некорректно полагаться исключительно на деструктор, то я согласен с этим. Там где можно следует использовать using или вызов Dispose руками.
Баг на своей машине вы увидите только если утечка быстрая, и приводит к ошибке. А если будет течь по хэндлу в 10 минут — то эффект будет противоположным. На своей машине все хорошо, продакшн раз в месяц умирает.
что значит не успеть вызвать финализатор?
и как вообще вызвать финализатор у конкретного объекта, кроме как потерять на него ссылку и вызвать сборку мусора?
это как и в каком .net?
В C# кажется вообще нельзя. Но с IL, как я знаю, можно.
в С++ можно менять значение константам :)) но это уже финт ушами ))
Вы про директиву препроцессора #define?
А почему нельзя из финализатора просто вызвать Dispose вместо того, чтобы освобождать ресурсы? Вроде, так аккуратнее должно выглядеть. Да и SuppressFinalize должен стать необязательным — ведь мы договорились, что Dispose умеет вызываться многократно. Или я что-то упустил?
UFO just landed and posted this here
Это все понятно. Я о другом. Пусть в Dispose мы делаем CloseHandle(...). Плюс проверяем, не был ли он уже закрыт и т.п. Тогда в финализаторе вместо явного вызова того же CloseHandle можно просто вызвать Dispose, который обо всем и позаботится. Нет?
UFO just landed and posted this here
UFO just landed and posted this here
SupressFinalize — это не вызов финализатора, это, наоборот, выкинуть объект из F-списка, чтобы его финализатор не вызывался (раз был вызван Dispose, то deterministic cleanup уже произошел).

Правда, иногда бывают ситуации, когда объект из состояния зомби (F-список) возвращается назад. Редко, но бывает. Для этого есть метод RegisterForFinalization или как-то так.

Я там ссылочку внизу давал, почитайте. Вообще есть ощущение, что про Dispose/Finalize в головах подавляющего большинства программистов полный ахтунг. Неудивительно, что потом .net обрастает разного рода легендами про то, какой он плохой
UFO just landed and posted this here
UFO just landed and posted this here
IDisposable и финализатор это разные вещи.

финалайз вызывается GC перед удалением объекта.
Dispose — просто обычный метод интерфейса IDisposable, который может быть вызван когда угодно и сколько угодно раз, а может быть и не вызван.

если вы хотите освободить все ресурсы, связанные с объектом, прямо сейчас — вызываете Dispose.

финализатор же не подразумевает немедленный вызов. объект вообще может не удаляться до закрытия приложения — так и висеть в памяти, занимая никому ненужные ресурсы и пямять.
Если перед смертью объекта вы всё-таки хотите вызвать освобождение ресурсов, то пропишите это в финализаторе.

самый оптимальный способ — вызывать Dispose в финализаторе.
самый оптимальный способ — вызывать Dispose в финализаторе.

Если я правильно помню Рихтера, то нужно быть аккуратным и не трогать любые managed ресуры во время работы финализатора.
Вот этот вариант мне кажется оптимальным
да, именно о нём я и говорил ))
Кто может написать правильный Финалайзер, Деструктор или Клозер (кому что больше нравится) на 100% выгрузку Microsoft.Office.Interop.Excel.Application из памяти?
System.Runtime.InteropServices.Marshall.FinalReleaseComObject?
Вам не все равно? В прикладных задачах до этих мелочей нет никакого дела.
Советую прочитать главу из отличной книги Джеффри Рихтера «CLR via C#», посвященную организации управления временем жизни объектов. Там описан универсальный паттерн реализации IDisposable и с использованием финализатора.
Краткое содержание:

class MyResource: IDisposable
{
private bool m_IsDisposed;
private IntPtr m_UnmanagedResource;

protected virtual void Dispose(bool disposing)
{
m_IsDisposed = true;

if (IntPtr.Zero != m_UnmanagedResource)
{
CloseHandle(m_UnmanagedResource);
m_UnmanagedResource = IntPtr.Zero;
}

if (disposing)
{
// Можно обращаться к другим объектам.
}
}

protected void CheckDiposed()
{
if (m_IsDisposed) throw new ObjectDisposedException(«MyRersource»);
}

public MyResource(IntPtr handle)
{
Contract.Requires(IntPtr.Zero != handle);
m_UnamanagedHandle = handle;
}

public vod DoWork()
{
CheckDisposed();
UnmanagedAPI(m_UnmanagedHandle);
}

public void Dispose()
{
Dispose(true);
}

~MyResource()
{
Dispose(false);
}
}
У Рихтера есть еще и вызов GC.SuppressFinalize, в комментариях. Без него уже разрушенный Dispose-ом объект зря переживет сборку мусора и получит лишний вызов Dispose уже из финализатора.
Sign up to leave a comment.

Articles