IDisposable implementation - Что нужно делать, если (распоряжаться)

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

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

Dispose method.

// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user 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).

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

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

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

  • Если я использую сторонние одноразовые объекты (например: devExpress winform controls), я не уверен, являются ли они управляемыми или неуправляемыми объектами. Скажем, я хочу избавиться от них при удалении формы. Как я могу узнать, что управляется, а какие - не управляемые объекты? Быть одноразовым не говорит об этом? В таких случаях, как решить, что должно входить и что должно выйти из предложения if (disposing)?

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

Edit

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

publisher.DoSomeEvent += subscriber.DoSomething;

Ответ 1

Что нужно делать, если (распоряжаться) '

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

  • if (disposed) {

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

  • if (disposing) {

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

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

  • }

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

  • disposed = true

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

  • Вероятно, это не должно идти, если эти коллекции не содержат объектов, которые необходимо удалить. Если они являются одноразовыми объектами, то они должны идти в блок "if disposing", и вы должны вызывать dispose по каждому элементу в коллекции.

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

  • Вам не следует обращаться к другим объектам управляемого кода при вызове финализатором, который находится за пределами блока "if (disposing)".

Ответ 6

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

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

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

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