Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
GBP
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
Почему вам не следует использовать фина…

Почему вам не следует использовать финализаторы

07 Окт 2016

Не так давно мы работали над диагностикой, связанной с проверкой финализатора, и у нас с коллегой возник спор по поводу деталей работы сборщика мусора и финализации объектов. И хотя я и он занимаемся разработкой на C# более 5 лет, к общему мнению мы не пришли, и я решил изучить этот вопрос подробнее.

0437_Finalization_ru/image1.png

Введение

Обычно первое знакомство с финализаторами у .NET разработчиков происходит, когда им нужно освободить неуправляемый ресурс. Возникает вопрос что же нужно использовать: реализовать в своём классе IDisposable или добавить финализатор? Тогда они идут, например, на StackOverflow и читают ответы на вопросы типа этого Finalize/Dispose pattern in C# где рассказывается про классический паттерн реализации IDisposable в сочетании с определением финализатора. Тот же самый паттерн можно найти и в MSDN в описании интерфейса IDisposable. Некоторые считают его довольно сложным для понимания и предлагают свои варианты вроде реализации очистки управляемых и неуправляемых ресурсов в отдельных методах или создания класса-обёртки специально для освобождения неуправляемого ресурса. Их можно найти на той же страничке на StackOverflow.

Большинство этих способов предполагают реализацию финализатора. Посмотрим какие плюсы и потенциальные проблемы это может принести.

Плюсы и минусы использования финализаторов

Плюсы.

  • Финализатор позволяет произвести очистку объекта перед тем как он будет удалён сборщиком мусора. Если разработчик забыл вызвать у объекта метод Dispose(), то в финализаторе можно освободить неуправляемые ресурсы и таким образом избежать их утечки.

Пожалуй, всё. Это единственный плюс, да и то спорный, о чём ниже.

Минусы.

  • Финализация недетерминированна. Вы не знаете, когда будет вызван финализатор. Прежде чем CLR начнёт финализировать объекты, сборщик мусора должен поместить их в очередь объектов, готовых к финализации, когда запустится очередная сборка мусора. А этот момент не определён.
  • В связи с тем, что объект с финализатором не удаляется сборщиком мусора сразу, он и весь граф связанных с ним объектов переживают сборку мусора и попадают в следующее поколение. Удалены они будут теперь тогда, когда сборщик мусора решит собрать объекты этого поколения, что может произойти очень нескоро.
  • Так как финализаторы выполняются в отдельном потоке параллельно работе других потоков приложения, то может возникнуть ситуация, когда новые объекты, требующие финализации, могут создаваться быстрее, чем будут отрабатывать финализаторы старых объектов. Это приведёт к увеличению потребляемой памяти, снижению производительности и, возможно, в итоге к падению приложения с OutOfMemoryException. Причём на машине разработчика вы можете никогда и не столкнуться с этой ситуацией, например, потому что у вас меньшее количество процессоров и объекты создаются медленнее или приложение работает не так долго, как в боевых условиях, и память не успевает закончиться. Можно потратить очень много времени на то чтобы понять, что причина была в финализаторах. Этот минус, пожалуй, перекрывает преимущества единственного плюса.
  • Если при выполнении финализатора возникнет исключение, то выполнение приложения экстренно завершится. Поэтому при реализации финализатора нужно быть особенно аккуратным: не обращаться к методам других объектов, для которых уже мог быть вызван финализатор; учитывать, что финализатор вызывается в отдельном потоке; проверять на null все другие объекты, которые потенциально могли принимать значение null. Последнее правило связано с тем, что финализатор может быть вызван для объекта в любом его состоянии, даже не до конца проинициализированном. Например, если вы всегда присваиваете в конструкторе новый объект в поле класса и потом ожидаете, что в финализаторе он всегда должен быть не равен null и обращаетесь к нему, то можно получить NullReferenceException, если при создании объекта в конструкторе базового класса возникло исключение и до выполнения вашего конструктора дело не дошло.
  • Финализатор может быть вообще не выполнен. При экстренном завершении приложения, например, при возникновении исключения в чужом финализаторе по причинам, описанным в предыдущем пункте, все остальные финализаторы не будут выполнены. Если вы в финализаторе освобождаете неуправляемые объекты операционной системы, то ничего плохого не произойдёт в том смысле что при завершении приложения система сама вернёт свои ресурсы. Но если вы сбрасываете недозаписанные байты в файл, то вы потеряете свои данные. Так что возможно лучше не реализовывать финализатор, а всегда допускать потерю данных в случае если забыли вызвать Dispose(), так как в этом случае проблему будет проще обнаружить.
  • Нужно помнить о том, что финализатор вызывается только один раз и если вы воскрешаете объект в финализаторе путём присваивания ссылки на него в другой живой объект, то возможно вам следует зарегистрировать его для финализации заново с помощью метода GC.ReRegisterForFinalize().
  • Вы можете нарваться на проблемы многопоточных приложений, например, состояние гонки, даже если ваше приложение однопоточное. Случай совсем уж экзотический, но теоретически возможный. Допустим в вашем объекте есть финализатор, и на него держит ссылку другой объект, у которого тоже есть финализатор. Если оба объекта становятся доступными для сборщика мусора, и их финализаторы начинают выполняться и другой объект воскрешается, то он и ваш объект снова становятся живыми. Теперь возможна ситуация, когда метод вашего объекта будет вызван из основного потока и одновременно из финализатора, так как он по-прежнему остался в очереди объектов, готовых к финализации. Код, который воспроизводит этот пример, приведён ниже. Можно увидеть как сначала выполняется финализатор объекта Root, потом финализатор объекта Nested, и после этого метод DoSomeWork() вызывается сразу из двух потоков.
class Root
{
    public volatile static Root StaticRoot = null;
    public Nested Nested = null;

    ~Root()
    {
        Console.WriteLine("Finalization of Root");
        StaticRoot = this;
    }
}
class Nested
{
    public void DoSomeWork()
    {
        Console.WriteLine(String.Format(
            "Thread {0} enters DoSomeWork",
            Thread.CurrentThread.ManagedThreadId));
        Thread.Sleep(2000);
        Console.WriteLine(String.Format(
            "Thread {0} leaves DoSomeWork",
            Thread.CurrentThread.ManagedThreadId));
    }
    ~Nested()
    {
        Console.WriteLine("Finalization of Nested");
        DoSomeWork();
    }
}

class Program
{
    static void CreateObjects()
    {
        Nested nested = new Nested();
        Root root = new Root();
        root.Nested = nested;
    }
    static void Main(string[] args)
    {
        CreateObjects();
        GC.Collect();
        while (Root.StaticRoot == null) { }
        Root.StaticRoot.Nested.DoSomeWork();
        Console.ReadLine();
    }
}

Вот что будет выведено на экран на моей машине:

Finalization of Root
Finalization of Nested
Thread 10 enters DoSomeWork
Thread 2 enters DoSomeWork
Thread 10 leaves DoSomeWork
Thread 2 leaves DoSomeWork

Если у вас финализаторы вызываются в другом порядке, попробуйте поменять местами создание nested и root.

Выводы

Финализаторы в .NET - это то место, где проще всего выстрелить себе в ногу. Прежде чем бросаться добавлять финализаторы для всех классов, реализующих IDisposable, стоит подумать, а действительно ли они так нужны. Надо отметить, что и сами разработчики CLR предостерегают от их использования на странице Dispose Pattern: "Avoid making types finalizable. Carefully consider any case in which you think a finalizer is needed. There is a real cost associated with instances with finalizers, from both a performance and code complexity standpoint."

Но если вы всё-таки решили использовать финализаторы, то PVS-Studio может помочь вам найти потенциальные ошибки. У нас есть диагностика V3100, которая покажет все места в финализаторе, где может возникнуть NullReferenceException.

Популярные статьи по теме
Главный вопрос программирования, рефакторинга и всего такого

Дата: 14 Апр 2016

Автор: Андрей Карпов

Вы угадали, ответ - "42". Здесь приводится 42 рекомендации по программированию, которые помогут избежать множества ошибок, сэкономить время и нервы. Автором рекомендаций выступает Андрей Карпов - тех…
Любите статический анализ кода!

Дата: 16 Окт 2017

Автор: Андрей Карпов

Я в шоке от возможностей статического анализа кода, хотя сам участвую в разработке инструмента PVS-Studio. На днях я был искренне удивлён тому, что анализатор оказался умнее и внимательнее меня.
Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей

Дата: 21 Ноя 2018

Автор: Андрей Карпов

Краткое описание технологий, используемых в инструменте PVS-Studio, которые позволяют эффективно обнаруживать большое количество паттернов ошибок и потенциальных уязвимостей. Статья описывает реализа…
Зло живёт в функциях сравнения

Дата: 19 Май 2017

Автор: Андрей Карпов

Возможно, читатели помнят мою статью под названием "Эффект последней строки". В ней идёт речь о замеченной мной закономерности: ошибка чаще всего допускается в последней строке однотипных блоков текс…
Как и почему статические анализаторы борются с ложными срабатываниями

Дата: 20 Мар 2017

Автор: Андрей Карпов

В своей предыдущей статье я писал, что мне не нравится подход, при котором статические анализаторы кода оцениваются с помощью синтетических тестов. В статье приводился пример, воспринимаемый анализат…
Эффект последней строки

Дата: 31 Май 2014

Автор: Андрей Карпов

Я изучил множество ошибок, возникающих в результате копирования кода. И утверждаю, что чаще всего ошибки допускают в последнем фрагменте однотипного кода. Ранее я не встречал в книгах описания этого …
Как PVS-Studio оказался внимательнее, чем три с половиной программиста

Дата: 22 Окт 2018

Автор: Андрей Карпов

PVS-Studio, как и другие статические анализаторы кода, часто выдаёт ложные срабатывания. Но не стоит спешить считать странные срабатывания ложными. Это короткая история о том, как PVS-Studio вновь ок…
PVS-Studio ROI

Дата: 30 Янв 2019

Автор: Андрей Карпов

Время от времени нам задают вопрос, какую пользу в денежном эквиваленте получит компания от использования анализатора PVS-Studio. Мы решили оформить ответ в виде статьи и привести таблицы, которые по…
Бесплатный PVS-Studio для тех, кто развивает открытые проекты

Дата: 22 Дек 2018

Автор: Андрей Карпов

В канун празднования нового 2019 года команда PVS-Studio решила сделать приятный подарок всем контрибьюторам open-source проектов, хостящихся на GitHub, GitLab или Bitbucket. Им предоставляется возмо…
PVS-Studio для Java

Дата: 17 Янв 2019

Автор: Андрей Карпов

В седьмой версии статического анализатора PVS-Studio мы добавили поддержку языка Java. Пришло время немного рассказать, как мы начинали делать поддержку языка Java, что у нас получилось и какие дальн…

Комментарии (0)

Следующие комментарии

На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Этот сайт использует куки и другие технологии, чтобы предоставить вам более персонализированный опыт. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Если вы не хотите, чтобы ваши данные обрабатывались, пожалуйста, покиньте данный сайт. Подробнее →
Принять