IDisposable реализация - Что должно идти в «если (утилизации)» - PullRequest
15 голосов
/ 04 октября 2011

Я исправил некоторые проблемы утечки памяти в приложении winforms и заметил некоторые одноразовые объекты, которые явно не удаляются (разработчик не вызвал метод Dispose). Реализация метода Finalize также не помогает, потому что она не входит в предложение if (disposing). Все отмены регистрации и очистки статических событий были добавлены в предложение if (disposing). Лучше всего вызывать Dispose, если объект одноразовый, но, к сожалению, это иногда случается

Если есть неуправляемые объекты, статические обработчики событий и некоторые управляемые коллекции, которые необходимо очистить при утилизации. Какой способ решить, что должно входить и что выходить из предложения if (disposing).

Метод утилизации.

// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected virtual void Dispose(bool disposing)
{
    if (!disposed)
    {
        if (disposing)
        {
            // Free other state (managed objects).
        }

         // Free your own state (unmanaged objects).
         // Set large fields to null.
         disposed = true;
     }
 }

В нем говорится, что управляемые объекты должны в if (disposing), что обычно выполняется только при явном вызове метода Dispose разработчиком. Если метод Finalize был реализован, и разработчик забыл вызвать метод Dispose, выполнение, выполняемое здесь через Finalizer, не попадает в раздел if (disposing).

Ниже приведены мои вопросы.

  1. Если у меня есть обработчики статических событий, которые вызывают утечки памяти, где мне их отменить? В или из пункта if (disposing)?

  2. Если у меня есть некоторые коллекции, которые вызывают утечки памяти, где я должен их очистить? В или из пункта if (disposing)?

  3. Если я использую одноразовые объекты третьих сторон (например, элементы управления winExpress devExpress), я не уверен, являются ли они управляемыми или неуправляемыми объектами. Допустим, я хочу избавиться от них при утилизации формы. Как я могу узнать, что является управляемыми и что такое неуправляемые объекты? Быть одноразовым не говорит об этом? В таких случаях, как решить, что должно входить и что выходить из предложения if (disposing)?

  4. Если я не уверен, что что-то управляемое или неуправляемое, каковы могут быть плохие последствия удаления / очистки / отмены регистрации событий из условия if (disposing)? Допустим, он проверяет нулевое значение перед удалением?

Редактировать

То, что я имею в виду под отменой регистрации события, выглядит примерно так: Publisher - это долгоживущий экземпляр, а нижняя строка находится в конструкторе подписчика. В этом случае подписчику необходимо отменить регистрацию события и утилизировать его перед издателем.

publisher.DoSomeEvent += subscriber.DoSomething;

Ответы [ 6 ]

3 голосов
/ 04 октября 2011

Ключ, который нужно запомнить, - цель IDisposable.Его задача - помочь вам детерминистически высвободить ресурсы, которые хранятся в вашем коде, за до объект является сборщиком мусора.Именно поэтому команда языка C # выбрала ключевое слово using, поскольку скобки определяют область, для которой объект и его ресурсы требуются приложению.

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

Второй сценарий - помочь с неуправляемым кодом.По сути, это как-то связано с вызовами API-интерфейса C ++ / C к операционной системе, и в этом случае вы несете ответственность за то, чтобы код не просочился.Поскольку большая часть .Net записывается просто для P / Invoke вплоть до существующего Win32 API, этот сценарий довольно распространен.Любой объект, который инкапсулирует ресурс из операционной системы (например, Mutex), будет реализовывать Disposer, чтобы позволить вам безопасно и детерминистически освобождать его ресурсы.

Однако эти API также реализуют деструктор, чтобы гарантировать, что если вы не используете ресурс правильно, то он не будет пропущен операционной системой.Когда вызывается ваш финализатор, вы не знаете, были ли другие объекты, на которые вы ссылались, уже сборщиком мусора, поэтому небезопасно делать вызовы функций к ним (так как они могут генерировать NullReferenceException), только неуправляемыессылки (которые по определению не могут быть собраны сборщиком мусора) будут доступны для финализатора.

Надеюсь, это немного поможет.

2 голосов
/ 04 октября 2011

В целом, управляемые ресурсы располагаются внутри if (disposing), а неуправляемые ресурсы - вне его.Шаблон утилизации работает следующим образом:

  1. if (disposed) {

    Если этот объект уже утилизирован, не выбрасывайте его во второй раз.

  2. if (disposing) {

    Если удаление было запрошено программно (true), утилизируйте управляемые ресурсы (IDisposable объекты), принадлежащие этому объекту.

    Если удаление было вызваносборщиком мусора (false) не следует утилизировать управляемые ресурсы, поскольку сборщик мусора, возможно, уже утилизировал принадлежащие им управляемые ресурсы и определенно утилизирует их до завершения работы приложения.

  3. }

    Утилизируйте неуправляемые ресурсы и освободите все ссылки на них.Шаг 1 гарантирует, что это происходит только один раз.

  4. disposed = true

    Отметьте этот объект как утилизированный, чтобы предотвратить повторную утилизацию.Повторное удаление может вызвать исключение NullReferenceException на шаге 2 или 3.

Вопрос 1
Ни в коем случае не выбрасывайте их в методе Dispose.Что произойдет, если вы избавитесь от нескольких экземпляров класса?Вы должны располагать статические элементы каждый раз, несмотря на то, что они уже удалены.Решение, которое я нашел, состояло в том, чтобы обработать событие AppDomain.DomainUnloaded и выполнить там статическое удаление.

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

Вопрос 3
IDisposable - это управляемый интерфейс.Если класс реализует IDisposable, это управляемый класс.Утилизируйте управляемые объекты внутри if (disposing).Если он не реализует IDisposable, он либо управляется и не требует утилизации, либо неуправляем и должен быть удален за пределы if (disposing).

Вопрос 4
Еслиприложение неожиданно завершает работу или не использует ручную утилизацию, сборщик мусора удаляет все объекты в случайном порядке.Дочерний объект может быть удален до того, как будет уничтожен его родитель, в результате чего родительский объект будет удален во второй раз.Большинство управляемых объектов можно безопасно утилизировать несколько раз, но только если они построены правильно.Вы рискуете (хотя и маловероятно) вызвать сбой коллекции gargabe, если объект будет размещен несколько раз.

0 голосов
/ 04 июля 2015

Что должно входить в 'if (утилизация)'

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

Причина в том, что процесс финализации сборщиков мусора может выполнить Dispose (false), если этот класс имеет Destructor.Обычно деструктор существует только при наличии неуправляемых ресурсов. Для завершения работы сборщика мусора нет определенного порядка выполнения метода Finalize.Таким образом, другие управляемые объекты могут не находиться в памяти к моменту завершения.

0 голосов
/ 04 октября 2011

Если единственная цель класса состоит в том, чтобы инкапсулировать некоторый ресурс (*), который необходимо очистить, если его оставить, у него не должно быть финализатора, и Dispose (bool) никогда не должен вызываться со значением False,но вызов Dispose (False) не должен иметь никакого эффекта.Если унаследованный класс должен содержать ресурс, требующий очистки, если его оставить, он должен инкапсулировать этот ресурс в объект, предназначенный исключительно для этой цели.Таким образом, если основной объект покинут, и никто больше не имеет ссылки на объект, инкапсулирующий ресурс, этот объект может выполнить его очистку без необходимости поддерживать основной объект живым в течение дополнительного цикла GC.

Кстати,Мне не нравится обработка Microsoft флага Disposed.Я хотел бы предложить, чтобы не виртуальный метод Dispose использовал целочисленный флаг с Interlocked.Exchange, чтобы гарантировать, что вызов Dispose из нескольких потоков приведет к тому, что логика dispose будет выполнена только один раз.Сам флаг может быть закрытым, но должно быть защищенное и / или открытое свойство Disposed, чтобы не требовалось, чтобы каждый производный класс реализовывал свой собственный флаг удаления.

(*) Ресурс не является каким-то конкретным типомсущность, а скорее свободный термин, который включает в себя все, что класс, возможно, попросил какую-то внешнюю сущность сделать от его имени, что нужно сказать этой внешней сущности, чтобы прекратить делать.Обычно внешняя сущность предоставляет классу исключительное использование чего-либо (будь то область памяти, блокировка, дескриптор GDI, файл, сокет, USB-устройство или что-то еще), но в некоторых случаях внешняяСущность, возможно, попросили утвердительно что-то сделать (например, запускать обработчик событий каждый раз, когда что-то происходит) или держать что-то (например, объектно-статическую ссылку на объект).«Неуправляемый» ресурс - это ресурс, который не будет очищен в случае его отмены.

Кстати, обратите внимание, что, хотя Microsoft и предполагала, что объекты, инкапсулирующие неуправляемые ресурсы, должны очищать их в случае их отказа, существуют некоторые типы ресурсов.для которого это действительно не практично.Рассмотрим объект, который хранит ссылку на объект, например, в статическом поле потока, и удаляет эту ссылку на объект, когда он имеет значение Dispose (удаление, естественно, должно произойти в потоке, в котором был создан объект).Если объект покинут, но поток все еще существует (например, в пуле потоков), цель статической ссылки потока может легко поддерживаться бесконечно долго.Даже если нет никаких ссылок на заброшенный объект, поэтому его метод Finalize () выполняется, для оставленного объекта будет трудно найти и уничтожить статическую ссылку на поток, находящуюся в каком-то потоке.

0 голосов
/ 04 октября 2011

Похоже, у вас в основном есть управляемые объекты, то есть объекты, которые реализуют IDisposable. Неуправляемый код - это такие вещи, как IntPtr или дескрипторы, которые обычно означают вызов неуправляемого кода или P / Invoke, чтобы получить к ним доступ.

  1. Как указал Махип, Dispose не предназначен для этого. Когда объект завершил получение событий, он должен отменить свою регистрацию. Если это невозможно, рассмотрите возможность использования WeakReferences.

  2. Скорее всего, это не должно происходить, если только в этих коллекциях нет объектов, которые нужно утилизировать. Если они являются одноразовыми предметами, то они должны быть в блоке «если выбрасывать», и вы должны вызывать dispose для каждого предмета в коллекции.

  3. Если он реализует IDisposable, он управляется

  4. Вы не должны получать доступ к другим объектам управляемого кода при вызове финализатором, что означает то, что находится за пределами блока «if (утилизация)».

0 голосов
/ 04 октября 2011

Если у меня есть обработчики статических событий, которые вызывают утечки памяти, где я должен отменить их регистрацию?В или из условия if (распоряжения)?

Метод Dispose вызывается в тех случаях, когда в качестве обработчика статических событий используются на уровне класса.Таким образом, вы не должны отменить их регистрацию.Обычно статический обработчик событий должен быть незарегистрирован, когда класс выгружается, или в какой-то момент во время выполнения приложения вы понимаете, что этот обработчик событий больше не требуется.

Для всех управляемых и неуправляемых ресурсов лучше реализовать шаблон IDisposable. См. Здесь http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspx и Шаблон Finalize / Dispose в C #

...