Безопасно ли вызывать GC.SuppressFinalize в финализаторе? - PullRequest
2 голосов
/ 22 сентября 2019

Поскольку тема finalizer/IDisposable и так называемый шаблон IDisposable имеют тенденцию вызывать множество позерства, понтификата и воинствующего мнения ( не - соответственно, здесь , здесь , здесь и т. Д.) Я действительно стесняюсь спросить это.В надежде выгрузить эти изношенные дебаты, я придерживаюсь очень простого вопроса, который, похоже, не дает краткого ответа на StackOverflow ...

Не вызывает ли GC.SuppressFinalize(this) пустое значение, когда финализатор объекта имеетначал выполнять?Более конкретно или полезно (конечно), безопасно ли вызывать GC.SuppressFinalize(this) из самого финализатора?(Опять же, мы не обсуждаем здесь «почему»)

Другими словами, помимо затрат на вызов API и его правильную установку флага в заголовке объекта, есть ли плохие, нежелательные,или иным образом ощутимая корректность или эффективность воздействия?

Ответы [ 2 ]

4 голосов
/ 22 сентября 2019

Конечно, было бы намного лучше вообще избежать финализатора и использовать SafeHandle, как диктует современная идиома.Тогда все эти вещи о финализаторах становятся полностью спорными.

Тем не менее, несмотря на мудрость этого, совершенно безопасно вызывать GC.SuppressFinalize() из финализатора. Документация для метода описывает, что делает метод:

Этот метод устанавливает бит в заголовке объекта obj, который среда выполнения проверяет при вызове финализаторов.

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

Проверка его позже, до вызова самого финализатора, также позволяет избежать финализации объекта, если выяснится, чтоФинализатор какого-то другого объекта свернулся, утилизируя его, даже если финализатор этого объекта был помещен в очередь финализации.

Обе эти проверки выполняются до вызова финализатора.Когда вызывается финализатор, бит в объекте не имеет смысла.Установка его безвредна, но ничего не даст.

В качестве отступления: обратите внимание, что в прошлых реализациях .NET использовались очереди Finalizer и FReachable.Когда объект был создан, если у него был финализатор, он был бы перемещен в очередь Finalizer.Если объект недоступен, он будет перемещен в очередь FReachable для последующей финализации.Вызов SuppressFinalize() удалит объект из очереди Finalizer.К тому времени, когда запускается финализатор, объект уже не находится в этой очереди, поэтому вызов SuppressFinalize() будет представлять собой NOP, также безвредный.

Теперь, как говорится, ваш вопрос широк: «… есть ли плохие, нежелательные или иным образом ощутимые эффекты корректности или производительности?» .Многое из этого в глазах смотрящего.Я бы сказал, что финализатор, который вызывает GC.SuppressFinalize(), неверен.Так что это будет "ощутимый эффект правильности" для меня.Я также считаю, что код, который отличается от опубликованных, признанных стандартных шаблонов, является «нежелательным».Без более конкретных критериев в вопросе, чтобы ограничить его, ответ на эту часть вопроса может быть любым из «да», «нет», «иногда» и т. Д.

На самом деле есть дублирующий вопросна ваш, но никто не соизволил ответить на него: Вызов GC.SuppressFinalize () из финализатора .Я нахожу поток комментариев по этому вопросу, особенно вклад Эрика Липперта:

Вы полагаете, что ненужный вызов SuppressFinalize - это ошибка в вашем плане.Это не проблема;проблема заключается в удалении управляемых ресурсов в потоке финализатора.Вспомните, что финализаторы работают в своем собственном потоке, и что управляемые ресурсы могут быть привязаны к потокам, и теперь начинайте представлять себе возможные ужасы.Более того: финализаторы работают в произвольном порядке.Управляемый объект, расположенный в потоке финализатора, возможно, уже завершен;теперь вы, возможно, выполняете логику финализации дважды на одном объекте;это устойчиво к этому сценарию?- Эрик Липперт, 31 марта '16 в 21:58 1

Написание правильного финализатора чрезвычайно сложно, и я рекомендую против вас пытаться когда-либо, в идеале, но определенно удерживайте, пока вы не поймете шаблон лучше.Если вы еще недостаточно напуганы, моя серия статей на эту тему может вызвать у вас больше страха: ericlippert.com / 2015/05/18 /… - Эрик Липперт, 31 марта '16 в 21:59

@ Том: Вопрос в том, что «я использую шаблон утилизации совершенно неправильно; эта конкретная часть того, что я делаю неправильно?» Нет, все неправильно с самого первого предложения .Вы не используете Dispose для удаления управляемых ресурсов, и вы, конечно, не используете финализатор для этого.Это проблема здесь.Есть ли что-то не так с вызовом SuppressFinalize из финализатора?Ну, это сработает, но не должно быть ситуации, в которой это правильно делать , поэтому неважно, работает она или нет.- Эрик Липперт, 7 июля в 14: 17

@ Том: Кроме того, почему вы сначала называете SuppressFinalize?Только потому, что это оптимизация производительности.Но при каких обстоятельствах это оптимизация при вызове из потока финализатора? Только когда вы не смогли выполнить эту оптимизацию из основного потока!Это место для этой оптимизации!- Эрик Липперт, 7 июля в 14: 24

ИМХО, эти комментарии подводят основную проблему к тонкости: вопрос о том, безопасно ли звонить SuppressFinalize() из финализатора, является неправильным вопросом.Если вы уже задали этот вопрос, код уже неправильный, и ответ на этот вопрос, вероятно, не так уж важен.Правильный подход заключается в исправлении кода, чтобы вам не приходилось задавать этот вопрос.

Наконец, хотя это не совсем та же проблема, я думаю, что стоит также указать, что обычное руководство вызывает SuppressFinalize()в конце Dispose() метод, вероятно, неверен.Если вызвано, оно должно быть вызвано в начале метода Dispose().См. Будьте осторожны, где вы положили GC.SuppressFinalize

2 голосов
/ 22 сентября 2019

Короче говоря, редко можно использовать Finalizer , вы должны очищать свое приложение детерминистически.Также есть условия гонки и другие причины, по которым финализация в .net проблематична.

Когда вам нужен финализатор, вы хотите его в дополнение к Dispose, а не вместо Dispose.

InВ большинстве случаев, если бы вы использовали финализатор , вы бы вызвали SuppressFinalize в вашем Dispose методе, но чтобы ответить на вопрос (и другие)

Является ли вызов GC.SuppressFinalize (this) пустым после начала выполнения финализатора объекта?

Вызов SuppressFinalize на самом деле довольно тривиален, он просто устанавливает флаг в объекте какпредотвратить его добавление в очередь завершения;Он имеет небольшие накладные расходы сам по себе;и может быть установлен в любом пути кода.Скорее всего, это не ваша проблема.

Однако настоящая проблема здесь заключается в том, что GC не может завершить , а ваш объект располагает вВ то же время и предположения о состоянии системы вокруг вас во время их выполнения.

Чтобы попытаться смягчить это, вы должны позвонить GC.SuppressFinalize(this), как только сможете, используйте флаг, чтобы определить, что вы утилизировали.или даже проверьте, начал ли appdomain или выгрузить или завершить работу

if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload())

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

...