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

Блок улова не оценивается, когда из окончаний выбрасываются исключения

Этот вопрос возник из-за того, что код, который работал ранее в .NET 4.0, не удался с необработанным исключением в .NET 4.5, частично из-за try/finallys. Если вы хотите получить подробную информацию, прочитайте больше на Microsoft connect. Я использовал его в качестве основы для этого примера, поэтому было бы полезно ссылаться.

Код

Для людей, которые решили не читать подробностей, стоящих за этим вопросом, здесь очень быстрый взгляд на условия, в которых это произошло:

using(var ms = new MemoryStream(encryptedData))
using(var cryptoStream = new CryptoStream(encryptedData, decryptor, CryptoStreamMode.Read))
using(var sr = new StreamReader(cryptoStream))

Эта проблема заключается в том, что существуют исключения, исключенные из метода Dispose CryptoStream (поскольку они находятся внутри оператора using, эти исключения случаются из двух разных блоков finally). Когда cryptoStream.Dispose() вызывается StreamReader, генерируется CryptographicException. Во второй раз cryptoStream.Dispose() вызывается, в своей инструкции использования он бросает ArgumentNullException

Следующий код удаляет большую часть ненужного кода из ссылки, приведенной выше, и разматывает операторы using в try/finally, чтобы четко показать, что они в конечном итоге блокируются.

using System;
using System.Security.Cryptography;
namespace Sandbox
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                try
                {
                    try
                    {
                        Console.WriteLine("Propagate, my children");
                    }
                    finally
                    {
                        // F1
                        Console.WriteLine("Throwing CryptographicExecption");
                        throw new CryptographicException();
                    }
                }
                finally
                {
                    // F2
                    Console.WriteLine("Throwing ArgumentException");
                    throw new ArgumentException();
                }
            }
            catch (ArgumentException)
            {
                // C1
                Console.WriteLine("Caught ArgumentException");
            }
            // Same behavior if this was in an enclosing try/catch
            catch (CryptographicException)
            {
                // C2
                Console.WriteLine("Caught CryptographicException");
            }

            Console.WriteLine("Made it out of the exception minefield");
        }
    }}

Примечание: Try/окончательно соответствует расширению с помощью операторов из ссылочного кода.

Вывод:

    Propagate, my children
    Throwing CryptographicExecption
    Throwing ArgumentException
    Caught ArgumentException
    Press any key to continue . . .

Кажется, что блок catch CryptographicException не выполняется. Однако удаление этого блока catch приводит к тому, что исключение завершает среду выполнения.

Немного больше информации

EDIT: обновление было обновлено до последней версии спецификации. Тот, с которым я столкнулся с MSDN, имел более старые формулировки. Lost обновлен до terminated.

Дайвинг в спецификации С#, разделы 8.9.5 и 8.10 обсуждают поведение исключений:

  • Когда генерируется исключение, в том числе из блока finally, управление передается в первое предложение catch в прилагаемой инструкции try. Это продолжается до тех пор, пока не будет найдено подходящее.
  • Если во время выполнения блока finally возникает исключение, и уже было распространено исключение, это исключение завершается

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

Я уверен, что вопрос здесь где-то

По большей части легко визуализировать, что делает среда выполнения. Код выполняет первый блок finally (F1), где генерируется исключение. По мере распространения исключения второе исключение выбрасывается из второго блока finally (F2).

Согласно спецификации, CryptographicException, выкинутый из F1, теперь завершен, а среда выполнения ищет обработчик для ArgumentException. Среда выполнения находит обработчик и выполняет код в блоке catch для ArgumentException (C1).

Вот где он становится туманным: спецификация говорит, что первое исключение будет прекращено. Однако, если второй блок catch (C2) удален из кода, CryptographicException, который предположительно был потерян, теперь является необработанным исключением, которое завершает работу программы. При наличии C2 код не будет завершен из необработанного исключения, поэтому на поверхности он, похоже, обрабатывает исключение, но фактически код обработки исключений в блоке никогда не выполняется.

Вопросы

Вопросы в основном одинаковы, но переформулированы для специфики.

  • Как получается, что CryptographicException завершается из-за исключения ArgumentException, выведенного из закрывающего блока finally, поскольку удаление блока catch (CryptographicException) приводит к тому, что исключение не обрабатывается и завершается среда выполнения?

  • Поскольку время выполнения, по-видимому, обрабатывает CryptographicException, когда присутствует блок catch (CryptographicException), почему код внутри блока не выполняется?


Дополнительная информация Редактировать

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

Другое любопытное поведение, которое случается при запуске кода с блоком catch (CryptographicException), закомментировано, - это разница между .NET 4.5 и .NET 3.5..NET 4.5 выбрасывает CryptographicException и завершает работу приложения..NET 3.5, однако, по-видимому, ведет себя больше в соответствии с спецификацией С#, где исключение.

Propagate, my children
Throwing CryptographicExecption

Unhandled Exception: System.Security.Cryptography.CryptographicException [...]
ram.cs:line 23
Throwing ArgumentException
Caught ArgumentException
Made it out of the exception minefield

В .NET 3.5 я вижу, что я читал в спецификации. Исключение становится "потерянным" или "завершенным", поскольку единственное, что когда-либо нужно поймать, это ArgumentException. Из-за этого программа может продолжить выполнение. У меня только .NET 4.5 на моей машине, интересно ли это происходит в .NET 4.0?

4b9b3361

Ответ 1

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

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

Быстрое резюме

Что я увидел на моей машине x64 Win7:

  • .NET v2.0-3.5 - диалог WER при нажатии CryptographicException. После нажатия Close the program программа продолжит работу, как если бы выполнение не было брошено. Приложение не завершено. Это поведение, которое можно было бы ожидать от чтения спецификации, и хорошо определен архитекторами, которые реализовали обработку исключений в .NET.

  • .NET v4.0-4.5 - не отображается диалог WER. Вместо этого появляется окно с вопросом, хотите ли вы отлаживать программу. При нажатии no программа немедленно завершается. После этого после этого не выполняются блоки.

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

Это не совсем то, что вы ожидаете

Кто бы мог подозревать отладчик Just-In-Time?

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

Исполняемый файл vsjitdebugger принудительно завершал приложение, а не позволял ему продолжить. В версии 2.0, dw20.exe не имеет такого поведения, на самом деле первое, что вы видите, это сообщение WER.

Благодаря отладчику jit, заканчивающему приложение, он сделал его показаться похожим на то, что он не соответствует тому, что говорит spec, когда на самом деле это происходит.

Чтобы проверить это, я отключил vsjitdebugger от запуска при сбое, изменив раздел реестра на HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Auto с 1 на 0. Конечно же, приложение проигнорировало исключение и продолжалось, как и .NET 2.0.

Running in .NET 4.0


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

  • Когда всплывающее окно "Just-In-Time Debugger" появится, отметьте Manually choose the debugging engines и нажмите "Да", который вы хотите отлаживать.
  • Когда Visual Studio предоставит вам параметры двигателя, нажмите "Отмена".
  • Это приведет к продолжению программы или появлению диалогового окна WER в зависимости от конфигурации вашего компьютера. Если это произойдет, говоря, что закрытие программы фактически не закроет ее, она будет продолжать работать, как будто все в порядке.

Ответ 2

Обработка исключений в .NET имеет 3 разных этапа:

  • Первый этап начинается с момента, когда выполняется команда throw. CLR ищет блок catch, который в области, который рекламирует, что он готов обрабатывать исключение. На этом этапе в С# код не выполняется. Технически можно выполнить код, но эта возможность не отображается в С#.

  • этап 2 начинается после того, как блокирует блок catch, и CLR знает, где выполняется выполнение. Затем он может надежно определить, какие окончательные блоки нужно выполнить. Любые кадры стека метода также разматываются.

  • Этап 3 начинается после завершения всех окончательных блоков, и стек разматывается методом, содержащим инструкцию catch. Указатель инструкции устанавливается в первый оператор в блоке catch. Если этот блок не содержит никаких дополнительных инструкций throw, выполнение возобновляется как обычно в заявлении, расположенном за блоком catch.

Итак, основное требование в вашем фрагменте кода заключается в том, что в области видимости есть исключение (CryptographicException). Без него стадия 1 терпит неудачу, и CLR не знает, как возобновить выполнение. Поток мертв, обычно также заканчивается программа в зависимости от политики обработки исключений. Ни один из блоков finally не будет выполнен.

Если на этапе 2 блок finally генерирует исключение, то обычная последовательность обработки исключений немедленно прерывается. Исходное исключение "потеряно", оно никогда не переходит на этап 3, поэтому в вашей программе не наблюдается. Обработка исключений начинается на этапе 1, теперь ищет новое исключение и начинается с области этого окончательного блока.

Ответ 3

Если во время выполнения блока finally выбрано исключение, и исключение уже было распространено, это исключение теряется

В основном, что происходит при выполнении:

  • CryptographicException во внутреннем конце.
  • Наконец, выполняется внешняя область и бросает ArgumentException. Поскольку "CryptographicException" был "оповещен" на данный момент времени, он потерян.
  • Заключительные уловы происходят, и ArgumentException улавливается.

... и не исключено, что первое исключение просто исчезнет в эфире, просто потому, что было другое исключение, которое было выбрано из другого блока finally.

Это именно то, что происходит на основе спецификации языка С#, которую вы цитировали. Первое исключение (CryptographicException) эффективно исчезает - оно "потеряно".

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

Это подробно объясняется в спецификации в 8.9.5 (текст в 8.10, который вы цитируете, относится к этому разделу):

Если блок finally выдает другое исключение, обработка текущего исключения прекращается.

Первое исключение, в вашем случае ArgumentException, в основном "исчезает".