Как обработать исключение, выброшенное из Dispose? - PullRequest
17 голосов
/ 23 июня 2009

Недавно я исследовал некоторые хитрые ошибки, связанные с неиспользуемым объектом.

Я нашел какой-то шаблон в коде. Сообщается, что некоторые m_foo не удаляются, хотя кажется, что все экземпляры SomeClass были удалены.

public class SomeClass: IDisposable
{
    void Dispose()
    {
       if (m_foo != null)
       {
          m_foo.Dispose();
       }
       if (m_bar != null)
       {
          m_bar.Dispose();
       }   
    }

    private Foo m_foo;

    private Bar m_bar;

}

Я подозреваю, что Foo.Dispose может выдать исключение, чтобы следующий код не выполнялся, поэтому m_bar не удаляется.

Так как Foo / Bar может быть от третьей стороны, поэтому не гарантируется, что он не выбрасывает исключение.

Если просто обернуть все вызовы Dispose с помощью try-catch, код окажется неуклюжим.

Как лучше всего справляться с этим?

Ответы [ 7 ]

28 голосов
/ 23 июня 2009

Это правда, что это может быть очень плохо - исключить исключение из вашего метода dispose, тем более что материал, который реализует IDisposable, обычно указывает финализатор, который будет вызывать Dispose.

Проблема заключается в том, что если устранить проблему под ковром, обработав исключение, у вас могут возникнуть очень сложные для отладки ситуации . Что если ваш IDisposable выделил критическую секцию, которая освобождается только после утилизации. Если вы игнорируете тот факт, что произошло исключение, вы можете оказаться в тупике центрального. Я думаю, что сбои в утилизации должны быть одним из тех случаев, когда вы хотите потерпеть неудачу рано, поэтому вы можете исправить ошибку, как только она обнаружена.

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

Вы действительно не хотите смести исключение OutOfMemoryException под ковер?

Если бы у меня был хитрый сторонний компонент, который произвольно выдавал исключения на Dispose, я бы его исправил И разместил в отдельном процессе, который я мог бы разрушить, когда он начал воспроизводиться.

3 голосов
/ 23 июня 2009

Если Dispose () вызывается внутри контекста финализации и выдает исключение, ваш процесс будет прерван.

Если вы подозреваете, что Foo.Dispose () генерирует исключение, я бы избавился от него последним, если это возможно, и поместил его в try / catch Сделайте все возможное, чтобы избавиться от него в улове - установите ссылку на ноль. Очень плохо генерировать исключения из Dispose (), и их следует избегать.

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

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

1 голос
/ 04 марта 2016

Согласно Правилам проектирования :

"Метод IDisposable.Dispose не должен вызывать исключение."

Так что, если ваша программа падает из-за необработанного исключения из Dispose () - см. Официальное решение

1 голос
/ 20 октября 2010

Утилизация не должна вызывать каких-либо исключений. Если это так - это не очень хорошо написано, так что ...

try { some.Dispose(); } catch {}

должно быть достаточно.

1 голос
/ 24 октября 2009

Поскольку вам не нужно выделять переменные в операторе using (), почему бы не использовать для этого операторы «stack»?

void Dispose()
{
    // the example in the question didn't use the full protected Dispose(bool) pattern
    // but most code should have if (!disposed) { if (disposing) { ...

    using (m_foo)
    using (m_bar)  
    {
        // no work, using statements will check null 
        // and call Dispose() on each object
    }

    m_bar = null;
    m_foo = null;
}

«Сложенные» операторы использования расширяются следующим образом:

using (m_foo)
{
    using (m_bar) { /* do nothing but call Dispose */ }
}

Таким образом, вызовы Dispose () помещаются в отдельные блоки finally:

try {
    try { // do nothing but call Dispose
    }
    finally { 
        if (m_bar != null)
            m_bar.Dispose(); 
    }
finally { 
    if (m_foo != null)
        m_foo.Dispose();
}

Мне было трудно найти ссылку на это в одном месте. «Сложенные» операторы использования находятся в старом сообщении в блоге Джо Даффи (см. Раздел «C # и VB Using Statement, семантика стека C ++»). На пост Джо Даффи ссылаются многие ответы StackOverflow на IDisposable. Я также обнаружил недавний вопрос , в котором сложение с использованием операторов для локальных переменных представляется распространенным явлением. Я нигде не мог найти цепочку блоков finally, кроме спецификации языка C # (раздел 8.13 в спецификации C # 3.0), и только для нескольких переменных в одном блоке 'using', что не совсем так Я предлагаю, но если вы дизассемблируете IL, вы обнаружите, что блоки try / finally являются вложенными. При проверке на нулевое значение, также из спецификации C #: «Если нулевой ресурс получен, то вызов Dispose не выполняется и исключение не выдается».

0 голосов
/ 20 августа 2016

В моем случае это произошло из-за того, что поток закрывал форму при доступе к элементу пользовательского интерфейса. Я восстановил это, прерывая поток на закрытии формы. (Событие «FormClosing»)

FormClosing += (o, e) => worker.Abort();
0 голосов
/ 18 февраля 2010

Чтобы не повторять код для удаления объектов, я написал следующий статический метод.

    public static void DisposeObject<T>(ref T objectToDispose) where T : class
    {
        IDisposable disposable = objectToDispose as IDisposable;
        if (disposable == null) return;

        disposable.Dispose();
        objectToDispose = null;
    }

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

В вашем случае вы можете добавить обработку исключений или создать другой вариант обработки исключений. Я бы удостоверился, что вы регистрируете / точку останова всякий раз, когда Dispose () генерирует исключения, но если вы не можете предотвратить это, то следующим лучшим решением будет убедиться, что проблема не распространяется.

...