Comments 65
GC.WaitForPendingFinalizers() у вас не срабатывает, потому что сам объект еще не собран сборщиком мусора.
Правильный код может быть таким:
Правильный код может быть таким:
temporaryFile = null;
GC.Collect();
GC.WaitForPendingFinalizers();
+14
точно: я забыл написать
рука отказалось такое писать :).
да, в таком случае этот способо гораздо более прост.
кстати, ведь не советуют вызывать GC.Collect(); или GC.WaitForPendingFinalizers();
утверждают, что это все равно только просьба к сборщику мусора, а когда он на самом деле захочет делать работу неизвестно.
или запутывают?
temporaryFile = null;
рука отказалось такое писать :).
да, в таком случае этот способо гораздо более прост.
кстати, ведь не советуют вызывать GC.Collect(); или GC.WaitForPendingFinalizers();
утверждают, что это все равно только просьба к сборщику мусора, а когда он на самом деле захочет делать работу неизвестно.
или запутывают?
+1
Это не просьба, а повеление.
Другое дело, что объекты, на которые есть ссылки, не будут собраны, но думаю, это и так понятно.
По поводу не вызывать — действительно, лучше не вызывать в реальном коде. Но мы ведем речь о тестах, здесь немного более широкие рамки допустимого
Другое дело, что объекты, на которые есть ссылки, не будут собраны, но думаю, это и так понятно.
По поводу не вызывать — действительно, лучше не вызывать в реальном коде. Но мы ведем речь о тестах, здесь немного более широкие рамки допустимого
+4
Убедили. Так что теперь у меня есть два способа тестировать финализатор.
Спасибо!
Спасибо!
0
И это не панацея. Сборщик мусора может решить, что чистить память совершенно необязательно. Чтобы отучить его думать самостоятельно, надо вызвать перегруженный метод Collect с GCCollectionMode.Forced.
Более того, даже подобный вызов у меня в 1 случае из 100, наверное, не помогает.
Более того, даже подобный вызов у меня в 1 случае из 100, наверное, не помогает.
0
Смотрим описание метода:
Collect() Forces an immediate garbage collection of all generations
Это то же самое, что и с флагом GCCollectionMode.Forced ( по крайней мере, в текущих версиях CLR)
То, что вызов не срабатывает — плохо, ищите проблему в своем коде.
Финализаторы в любом случае должны отработать по завершению программы
Collect() Forces an immediate garbage collection of all generations
Это то же самое, что и с флагом GCCollectionMode.Forced ( по крайней мере, в текущих версиях CLR)
То, что вызов не срабатывает — плохо, ищите проблему в своем коде.
Финализаторы в любом случае должны отработать по завершению программы
0
Кстати, это только в DEBUG режиме нужно null присваивать, или в RELEASE тоже?
Ведь если нет ссылки на объект, то вроде бы зачем присваивать null?
Ведь если нет ссылки на объект, то вроде бы зачем присваивать null?
0
В любом режиме нужно, если вы хотите гарантировать, что ссылки на объект больше нет.
Область видимости переменной temporaryFile — до конца метода, поэтому сохраненная в нем ссылка может существовать на любом промежутке от последнего присваивания до закрывающей фигурной скобки.
Чтоб гарантировать, что ссылки больше нет, необходимо присвоить null или другое значение, таким образом потеряется старое, и сборщик мусора будет вправе удалить такой объект
Область видимости переменной temporaryFile — до конца метода, поэтому сохраненная в нем ссылка может существовать на любом промежутке от последнего присваивания до закрывающей фигурной скобки.
Чтоб гарантировать, что ссылки больше нет, необходимо присвоить null или другое значение, таким образом потеряется старое, и сборщик мусора будет вправе удалить такой объект
0
А как же все эти трюки, что таймер (созданный в main) запущенный в DEBUG режиме работает до конца программы, но в RELEASE режиме умирает довольно быстро?
0
Это именно трюки, т.к. точное время сборки объекта никто не гарантирует.
Как я уже сказал, ссылка будет потеряна не раньше, чем ее последнее использование. В релизе производятся более агрессивные оптимизации, поэтому ссылка может быть потеряна сразу же (но гарантии никакой нет).
Если вы хотите продлить жизнь ссылки, посмотрите на GC.KeepAlive()
Как я уже сказал, ссылка будет потеряна не раньше, чем ее последнее использование. В релизе производятся более агрессивные оптимизации, поэтому ссылка может быть потеряна сразу же (но гарантии никакой нет).
Если вы хотите продлить жизнь ссылки, посмотрите на GC.KeepAlive()
0
При компиляции с параметром debug+ (или наличии атрибута сборки System.Diagnostics.DebuggableAttribute) JIT считает что время жизни локальной переменной — до завершения метода. В противном случае, JIT оптимизирует код, а GC, не найдя ссылок на переменную, выполнит финализацию.
+1
В первый вызов GC.Collect() будут собраны только те объекты, у которых нет финализатора. Затем во втором вызове вы принуждаете финализаторы отработать. И вот тут нужно еще раз вызвать GC.Collect(), чтобы сборщик собрал объекты, финализаторы которых отработали. Итого:
temporaryFile = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
temporaryFile = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
-3
и кстати, совет с исключением в финализаторе хороший, годный, по крайней мере в дебаге (в релизе лучше все-таки тихо вызвать Dispose)
кажется, встречал подобный совет еще у Рихтера
кажется, встречал подобный совет еще у Рихтера
0
В финализаторе исключения игнорируются.
+2
Игнорировалсь до .NET 2.0
Сейчас исключение в финализаторе прерывает процесс
Сейчас исключение в финализаторе прерывает процесс
+1
Совет был писать нечто вроде Debug.WriteLine(«AAA!»)…
0
Этот совет подходит не для всех объектов и не для всех случаев. Например, если среднее время работы программы — 100мс, то большинство объектов можно вообще не закрывать и даже не собирать — ОС все сама почистит после завершения процесса.
0
Если ваша ОС — Windows, то она в любом случае почистит все после завершения процесса.
Вопрос не в том, чтобы «все почистить», а в том, чтобы писать корректный код, который «убирает за собой».
Финализатор просто помогает найти те объекты, которые не были обернуты в using, например
Вопрос не в том, чтобы «все почистить», а в том, чтобы писать корректный код, который «убирает за собой».
Финализатор просто помогает найти те объекты, которые не были обернуты в using, например
0
Зачем писать корректный код, который «убирает за собой», дублируя функциональность ОС?
-1
Можно назвать несколько причин:
1. Это хорошая привычка. Не всегда вы пишите программы, которые потребляют мало ресурсов и поэтому можно мусорить где угодно
2. Компонентная архитектура. Сегодня ваш код используется в консольном приложении, а завтра — в высоконагруженном сервисе, где ресурсы можно и нужно освобождать побыстрее
3. Глубокое понимание системы. По моему опыту, программисты, которые не следят за ссылками/указателями/ресурсами поступают так не потому, что знают что-то о механизмах завершения процесса, а потому что просто не способны на это
4. Эстетическая составляющая. У каждой открывающей скобки должна быть закрывающая, а у каждого конструктора — деструктор
1. Это хорошая привычка. Не всегда вы пишите программы, которые потребляют мало ресурсов и поэтому можно мусорить где угодно
2. Компонентная архитектура. Сегодня ваш код используется в консольном приложении, а завтра — в высоконагруженном сервисе, где ресурсы можно и нужно освобождать побыстрее
3. Глубокое понимание системы. По моему опыту, программисты, которые не следят за ссылками/указателями/ресурсами поступают так не потому, что знают что-то о механизмах завершения процесса, а потому что просто не способны на это
4. Эстетическая составляющая. У каждой открывающей скобки должна быть закрывающая, а у каждого конструктора — деструктор
+2
Эти все причины — лишь рекомендации. Я все же думаю, что не стоит запрещать программисту бросать объекты без вызова Dispose в короткоживущих процессах.
Кстати, вот придумал только что еще одну причину не бросать исключение в деструкторе, даже в DEBUG:
-2. Программист может пожелать сделать из intermediate window пару вызовов следующего плана:
Кстати, вот придумал только что еще одну причину не бросать исключение в деструкторе, даже в DEBUG:
-2. Программист может пожелать сделать из intermediate window пару вызовов следующего плана:
new SomeCriticalObject(1, 2, "foo.txt").Bar();
Но синтаксис intermediate window не позволяет сделать еще и Dispose без сохранения объекта в промежуточную переменную — а удобной переменной под рукой может и не оказаться.0
new SomeCriticalObject(1, 2, "foo.txt").Bar();
это не должно быть причиной. Такой интерфейс надо поменять и получить
SomeCriticalObject.StaticBar(1,2,"foo.txt")
А внутри него все вполне можно за собой убрать.
-1
GC не гарантирует вызов финализатора при завершении приложения.
stackoverflow.com/questions/9941688/arent-destructors-guaranteed-to-finish-running
stackoverflow.com/questions/9941688/arent-destructors-guaranteed-to-finish-running
-1
Временный файл, созданный на диске, не будет «почищен».
0
К тому же процессы иногда живут долго: в моем случае месяцами. Так что приходится очищать все что можно и до завершения процесса.
0
К сожалению, AppDomainы работают слишком медленно.
+1
в данном случае это не критично: таких тестов обычно не много.
+1
Как сказать. Я вот однажды тоже в свою программу добавил несколько операций с доменами приложений. Предполагалось, что пользователь будет работать с программой долго, и пары секунд накладных расходов в фоновом потоке никто не заметит.
Но уже через 2 месяца эксплуатации выяснилось, что программа «слишком долго закрывается» — в 80% случаях ее запускали ради нажатия одной кнопки.
Но уже через 2 месяца эксплуатации выяснилось, что программа «слишком долго закрывается» — в 80% случаях ее запускали ради нажатия одной кнопки.
0
Тестировать в вашем случае надо Dispose, а не финализатор (потому что в финализаторе у вас тривиальный код). Тогда и проблем будет намного меньше.
+6
Именно так. Финализатор вообще не является контрактом класса, в отличии от Dispose.
0
не совсем понимаю, почему я не должен тестировать финализатор: в нем есть какой то код (пусть и тривиальный). я хочу быть уверен, что используемый ресурс будет правильно «закрываться» в финализаторе, и что другой программист его не сломает.
0
Потому что количество усилий на это непропорционально результату.
+5
в общем случае может быть, но в моем конкретном случае, несколько раз случались проблемы из за того, что ресурс не был освобожден, а тесты этого даже и не проверяли.
в любом случае идея этого поста: как тестировать финализатор, когда это надо.
в любом случае идея этого поста: как тестировать финализатор, когда это надо.
+1
А Dispose у вас при этом был покрыт тестами?
0
Да. Реализация Dispose покрыта тестом.
0
Значит нужно тестировать, что у вас явно вызывается Dispose (не из финализатора, а из кода-потребителя). Не диспозить disposable — зло.
0
Согласен на все 100%. Но никто не может мне гарантировать, что Dispose всегда будет вызываться. Следовательно, небольшое усилие (финализатор и его тест), по крайне мере, минимизуруют ущерб от не вызова Dispose.
+1
Вопрос в том, что является небольшим усилием.
Загрузка и опускание аппдомена в каждом тесте — это не «небольшое усилие».
Загрузка и опускание аппдомена в каждом тесте — это не «небольшое усилие».
0
Согласен. Но в данном случае я имел ввиду усилия программиста приготовить такой тест.
В любом случае, таких тестов (для финализатора) не так много.
В любом случае, таких тестов (для финализатора) не так много.
0
Ну да, а еще нарушение принципа единого места ошибки — поскольку вы не можете проверить, был ли вызван Dispose, вы проверяете, выполнил ли Dispose свою работу, что приводит к дублированию проверяющего кода.
0
Не понимаю: я проверяю, что Dispose, когда он вызывается «закрывает» ресурс. С другой стороны, я проверяю, что когда Dispose не вызывается, то финализатор «закрывает» ресурс. Где тут дублирование, с учетом, что это не совсем unit тест?
0
Может лучше было потратить усилия на то чтобы тестировать вызов Dispose во всех местах где он должен вызываться?
+1
Хорошо бы. И я пытаюсь это делать. Но как я могу быть уверен на 100%, что Dispose всегда вызывается? Что он и завтра всегда будет вызываться?
+1
Так вы и на 100% не можете быть увереным что финализатор вообще вызовется. Платформа вам этого не гарантирует.
+1
По крайней мере в тех случаях, когда финализатор вызывается, я буду уверен, что он делает то, что я от него ожидаю.
И как я понимаю, в подавляющем числе случаев он все таки вызывается.
И как я понимаю, в подавляющем числе случаев он все таки вызывается.
0
Так если вы протестируете логику Dispose, то автоматически протестируете и логику финализатора. А то, что вы делаете, так это проверяете, что финализатор срабатывает, т.е. пишете юнит-тест не на свою бизнес-логику, а на CLR. С таким подходом имеет смысл и такие тесты писать.
int i = 5
Assert.True(i == 5)
0
Помоему, в старых версиях финализатор еще вызывался из своего высокоприоритетного потока, поэтому у ms еще был хитрый паттерн, если хочется использовать финализатор в связке с Dispose.
0
должен удалять его в Dipose()Наверное имелось ввиду Dispose()?
-1
Sign up to leave a comment.
Как тестировать код финализатора (c#)