Подтвердить что ты не робот

Как обрабатывать исключение, выбрасываемое из Dispose?

Недавно я изучал некоторые сложные ошибки об объекте, который не был удален.

Я нашел некоторый шаблон в коде. Сообщается, что некоторые 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, код окажется неуклюжим.

Какая наилучшая практика для этого?

4b9b3361

Ответ 1

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

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

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

Неужели вы действительно не хотите, чтобы под маркой OutOfMemoryException?

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

Ответ 2

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

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

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

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

Ответ 3

Поскольку вам не нужно выделять переменные в операторе using() - почему бы не использовать "stacked" с помощью операторов для этого?

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;
}

Операторы сложения "stacked" расширяются следующим образом:

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();
}

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

Ответ 4

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

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

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

Ответ 5

Согласно Правила оформления:

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

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

Ответ 6

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

    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.

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

Ответ 7

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

FormClosing += (o, e) => worker.Abort();