Использует ли оператор C # аварийный вывод? - PullRequest
27 голосов
/ 13 октября 2010

Я только что закончил читать "C # 4.0 в двух словах" (О'Рейли), и я думаю, что это отличная книга для программиста, желающего перейти на C #, но это заставило меня задуматься.Моя проблема заключается в определении оператора using.Согласно книге (стр. 138),

using (StreamReader reader = File.OpenText("file.txt")) {
    ...
}

точно эквивалентно:

StreamReader reader = File.OpenText("file.txt");
try {
    ...
} finally {
    if (reader != null)
        ((IDisposable)reader).Dispose();
}

Предположим, однако, что это верно и что этот код выполняется в отдельномнить.Этот поток теперь прерван с помощью thread.Abort(), поэтому выбрасывается ThreadAbortException и предполагается, что поток находится точно после инициализации считывателя и перед вводом предложения try..finally.Это будет означать, что читатель не настроен!

Возможное решение будет заключаться в следующем кодировании:

StreamReader reader = null;
try {
    reader = File.OpenText("file.txt");
    ...
} finally {
    if (reader != null)
        ((IDisposable)reader).Dispose();
}

Это будет безопасным для прерывания.

Теперь длямои вопросы:

  1. Являются ли авторы книги правильными, а утверждение using небезопасно или они ошибочны, и оно ведет себя как во втором решении?
  2. Если using эквивалентно первому варианту (небезопасно при прерывании), почему он проверяет null в finally?
  3. Согласно книге (стр. 856), ThreadAbortException может быть выброшеногде-нибудь в управляемом коде.Но, может быть, есть исключения, и первый вариант в конце концов безопасен при прерывании?

РЕДАКТИРОВАТЬ: Я знаю, что использование thread.Abort() не считается хорошей практикой.Мой интерес чисто теоретический: как оператор using ведет себя точно ?

Ответы [ 9 ]

17 голосов
/ 13 октября 2010

На сопутствующем веб-сайте книги больше информации об отмене тем здесь .

Короче говоря, первый перевод верен (это можно увидеть, посмотрев на IL).

Ответ на ваш второй вопрос заключается в том, что могут быть сценарии, в которых переменная может быть на законном основании нулевой. Например, GetFoo () может возвращать здесь значение null, в котором вы не хотите, чтобы исключение NullReferenceException вызывалось в неявном блоке finally:

using (var x = GetFoo())
{
   ...
}

Чтобы ответить на ваш третий вопрос, единственный способ обезопасить Abort (если вы вызываете код Framework) - это впоследствии разрушить AppDomain. Во многих случаях это действительно практичное решение (именно это делает LINQPad всякий раз, когда вы отменяете текущий запрос).

8 голосов
/ 13 октября 2010

Между двумя вашими сценариями нет никакой разницы - во втором случае ThreadAbort все еще может произойти после вызова OpenText, но до того, как результат будет назначен читателю.

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

В ответ на ваши изменения - я бы еще раз отметил, что ваши два сценария фактически идентичны. Переменная 'reader' будет нулевой, если вызов File.OpenText успешно не завершится и не вернет значение, поэтому нет никакой разницы между написанием кода первым способом по сравнению со вторым.

6 голосов
/ 13 октября 2010

Thread.Abort - это очень, очень плохо, джиу; если люди звонят, что у вас уже много проблем (неисправимые блокировки и т. д.). Thread.Abort действительно должно быть ограничено сканерией болезненного процесса.

Исключения составляют , как правило, , развернутые без ошибок, но в крайних случаях нет гарантии, что каждый бит кода может быть выполнен Более неотразимый пример: «что произойдет, если отключится питание?».

Проверка null; что если File.OpenText вернул null? Хорошо, это не будет , но компилятор этого не знает.

4 голосов
/ 13 октября 2010

Немного оффтоп, но поведение оператора блокировки во время прерывания потока также интересно. Хотя блокировка эквивалентна:

object obj = x;
System.Threading.Monitor.Enter(obj);
try {
    …
}
finally {
    System.Threading.Monitor.Exit(obj);
}

Гарантируется (JITter x86), что прерывание потока не происходит между Monitor.Enter и оператором try.
http://blogs.msdn.com/b/ericlippert/archive/2007/08/17/subtleties-of-c-il-codegen.aspx

Кажется, что сгенерированный код IL отличается от .net 4:
http://blogs.msdn.com/b/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx

2 голосов
/ 02 марта 2011

Являются ли авторы книги правильными, и утверждение об использовании небезопасно или они ошибочны, и оно ведет себя как в моем втором решении?

Согласно книге (стр. 856), ThreadAbortException может быть выброшено в любом месте управляемого кода. Но, может быть, есть исключения, и первый вариант в конце концов безопасен при прерывании?

Авторы правы. Блок using небезопасен. Ваше второе решение также небезопасно, поток может быть прерван в середине процесса получения ресурса.

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

A Thread.Abort будет ожидать только кода, выполняющегося внутри Области ограниченного выполнения (CER), блоков finally, блоков catch, статических конструкторов и неуправляемого кода. Так что это безопасное решение для сброса ( только в отношении приобретения и распоряжения ресурсом):

StreamReader reader = null;
try {
  try { }
  finally { reader = File.OpenText("file.txt"); }
  // ...
}
finally {
  if (reader != null) reader.Dispose();
}

Но будьте осторожны , код аварийного завершения должен выполняться быстро и не блокировать . Это может повредить целую операцию выгрузки домена приложения.

Если использование эквивалентно первому варианту (небезопасно), почему оно проверяет наличие нуля в finally?

Проверка на нулевое значение делает шаблон using безопасным при наличии ссылок null.

2 голосов
/ 13 октября 2010

Вы сосредоточены на неправильной проблеме.ThreadAbortException также может прервать метод OpenText ().Вы можете надеяться, что оно устойчиво к этому, но это не так.Методы каркаса не имеют предложения try / catch, которые пытаются обработать прерывание потока.

Обратите внимание, что файл не остается открытым вечно.Финализатор FileStream, в конце концов, закроет дескриптор файла.Конечно, это может вызвать исключения в вашей программе, когда вы продолжаете работать и пытаетесь открыть файл еще раз до запуска финализатора.Хотя это то, что вы всегда должны защищать при работе в многозадачной операционной системе.

2 голосов
/ 13 октября 2010

В спецификации языка четко указано, что первая верна.

http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx MS Spec (документ Word)http://www.ecma -international.org / публикации / файлы / ECMA-ST / Ecma-334.pdf Спецификация ECMA

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

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

0 голосов
/ 13 октября 2010

оператор finally всегда выполняется, MSDN говорит, что "finally используется для гарантии того, что блок оператора выполняется из кода независимо от того, как был выполнен предыдущий блок try."

Так что вам не нужно беспокоиться об отсутствии очистки ресурсов и т. Д. (Только если Windows, Framework-Runtime или что-то еще плохое, что вы не можете контролировать, происходят, но тогда есть более серьезные проблемы, чем очистка ресурсов ;-))

0 голосов
/ 13 октября 2010

Первое действительно точно соответствует последнему.

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

ThreadAbort - это управляемое исключение, которое среда выполнения будет вызывать, когда это возможно, и только тогда.

Тем не менее, как только вы попадаете в ThreadAbort, зачем пытаться очистить? В любом случае ты в муках смерти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...