Как обрабатывать исключение, выбрасываемое из 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, код окажется неуклюжим.
Какая наилучшая практика для этого?
Ответы
Ответ 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();