Каков рекомендуемый способ защиты от утечек ресурсов в контексте ThreadAbortException?
Я работаю над улучшением исключительной безопасности части кода, и я понял, что поднял ThreadAbortException
может привести к нежелательным утечкам ресурсов, даже при защите ресурсов с помощью C# using
построить. Например, рассмотрим следующий код (который может быть запущен в отдельном потоке).
using (TextWriter writer = CreateWriter(filename))
{
// do something with the writer.
}
TextWriter CreateWriter(string filename)
{
return new CustomWriter(File.OpenWrite(filename));
}
Если поток, выполняющий этот код, ненормально прерван, то я хотел бы, чтобы дескриптор файла ссылался на filename
быть закрытым немедленно. Могу ли я сделать это без замены использования using
построить с блоком try / finally?
Я предполагаю, что ThreadAbortException
может быть поднят в любое время, что означает, что я должен обращать внимание на то, что происходит между утверждениями. Пока я могу защититься от исключения в CreateWriter
с блоком try / finally, using
Конструкция не будет делать то же самое до тех пор, пока не будет вычислено выражение в скобках, что означает, что файловый ресурс остается открытым, если исключение происходит сразу после CreateWriter
возвращается.
Я понимаю, что финализатор в конечном итоге выпустит дескриптор файла, но мне интересно, есть ли детерминистский способ решения этой проблемы без перехвата ThreadAbortException
в каждом месте CreateWriter
используется.
4 ответа
Здесь есть компромисс.
- Обязательно закройте все ресурсы немедленно, даже при наличии ThreadAbortException
- Имейте более простой код, но временно теряйте ресурсы, если вызывается Abort()
Я предполагаю, что вы не звоните Abort, а просто хотите быть в безопасности, если это делает кто-то другой. Если вы звоните Abort, я бы посоветовал вам этого не делать. Это не единственная проблема, с которой вы столкнетесь. Есть другие проблемы с Abort в документации.
# 2 - правильный выбор, потому что вызывающие Abort() должны этого ожидать.
Если вы хотите выбрать #1, то я не думаю, что даже простая попытка / уловка поможет. Если ThreadAbortException может происходить везде, то это может произойти и после открытия файла (внутри File.OpenWrite()) и до того, как вы можете назначить его переменной, для которой вы можете вызвать Dispose() - у вас будет та же проблема как в вашем коде.
Вам нужна семантика, как
using (var handle = GetUnOpenedHandle()) {
handle.Open(); // this can't involve assignment to any field of handle
}
Я не уверен, что это возможно.
Да, детерминированный способ предотвратить это, не используя Thread.Abort
, Когда-либо. Сигнал вашим потокам, что пора останавливаться, и пусть они завершаются изящно. Thread.Abort
большая большая красная сельдь, помещенная в API исключительно для того, чтобы вас сбить с толку.;)
http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation
Во многих случаях (но определенно не во всех) вы могли бы защититься от ThreadAbortException
, Большая часть критического кода в.NET BCL уже достаточно хорошо справляется с этой задачей. Проблема в том, что это действительно трудно понять правильно. И по этой причине большинство людей рекомендуют, и это правильно, избегать прерывания потоков. Начиная с версии 2.0, CLR сделал прерывания потока намного более терпимыми и представил новый набор API, чтобы помочь авторам кода защититься от них. Посмотрите на Области ограниченного выполнения для более глубокого взгляда на то, как все это работает.
Я полагаю, что вы правы в своих проблемах на примере using
блок. Для корректной работы ограниченных областей выполнения внеполосное (асинхронное) исключение должно происходить изнутри try
блок. Но из-за того, как using
расширяет выражение вычисляется вне try
блок. Сравните это с расширением lock
блок, который оценивает выражение изнутри try
блок. В любом случае, это верно для версии 4.0 платформы, и это было изменено специально для защиты от этих исключений.
Таким образом, вопрос в том, почему то же самое изменение не было сделано с using
блок. По словам Джо Даффи, это было приемлемым упущением, поскольку предполагалось, что за прерыванием потока всегда должно следовать завершение домена приложения, который в любом случае будет запускать финализаторы.
Так да. Ваш код не терпит внеполосных (асинхронных) исключений. Но преобладающая мудрость тех, кто умнее меня, заключается в том, что так не должно быть.
Прерывание потока чаще всего используется в случае фатальной ошибки, поэтому ваш ответ, вероятно, должен позволить вашему приложению завершиться. Если вы пытаетесь остановить собственные потоки, используйте Thread.Join().