C # Время окончательного исполнения - PullRequest
21 голосов
/ 20 мая 2009

Возьмите этот код:

using System;

namespace OddThrow
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                throw new Exception("Exception!");
            }
            finally
            {
                System.Threading.Thread.Sleep(2500);
                Console.Error.WriteLine("I'm dying!");
                System.Threading.Thread.Sleep(2500);
            }
        }
    }
}

Что дает мне этот вывод:

Unhandled Exception: System.Exception: Exception!
   at OddThrow.Program.Main(String[] args) in C:\Documents and Settings\username
\My Documents\Visual Studio 2008\Projects\OddThrow\OddThrow\Program.cs:line 14
I'm dying!

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

  1. Необработанное исключение, текст / сообщение
  2. Наконец блоки.
  3. Завершить приложение

В соответствии со стандартом C #, §8.9.5, это поведение является неправильным:

  • В текущем члене функции проверяется каждый оператор try, содержащий точку выброса. Для каждого оператора S, начиная с самого внутреннего оператора try и заканчивая самым внешним оператором try, оцениваются следующие шаги:
    • Если блок try S содержит точку выброса и если S имеет одно или несколько предложений catch, предложения catch проверяются в порядке появления, чтобы найти подходящий обработчик для исключения. Первое предложение catch, которое указывает тип исключения или базовый тип типа исключения, считается совпадающим. Общее предложение catch (§8.10) считается совпадением для любого типа исключения. Если найдено соответствующее предложение catch, распространение исключения завершается передачей управления блоку этого предложения catch.
    • В противном случае, если блок try или блок catch S содержит точку выброса и если S имеет блок finally, управление передается блоку finally. Если блок finally вызывает другое исключение, обработка текущего исключения прекращается. В противном случае, когда управление достигает конечной точки блока finally, обработка текущего исключения продолжается.
  • Если обработчик исключений не был найден в текущем вызове члена функции, вызов члена функции завершается. Затем шаги, описанные выше, повторяются для вызывающей стороны члена функции с точкой выброса, соответствующей оператору, из которого был вызван член функции.
  • Если обработка исключения завершает все вызовы членов функции в текущем потоке, указывая на то, что у потока нет обработчика для исключения, тогда сам поток завершается. Влияние такого завершения определяется реализацией.

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

Ответы [ 10 ]

7 голосов
/ 20 мая 2009

Заявления стандарта о порядке исполнения верны и не противоречат тому, что вы наблюдаете. Сообщение «Необработанное исключение» разрешено появляться в любой точке процесса, поскольку это просто сообщение от CLR, а не сам обработчик исключения. Правила порядка выполнения применяются только к коду, выполняемому внутри CLR, а не к тому, что делает сам CLR.

Что вы на самом деле сделали, так это раскрыли детали реализации, а именно то, что необработанные исключения распознаются, просматривая стек, в котором находятся блоки try {}, а не фактически исследуя весь путь до корня. При просмотре этого стека исключения могут или не могут быть обработаны , но необработанные исключения распознаются таким образом.

Как вы, возможно, знаете, если вы добавите в свою основную функцию команду try {} catch {} верхнего уровня, то увидите ожидаемое поведение: каждая функция будет наконец выполнена перед проверкой следующего кадра на соответствующий улов {}.

2 голосов
/ 20 мая 2009

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

http://bartdesmet.net/blogs/bart/archive/2006/04/04/clr-exception-handling-from-a-to-z-everything-you-didn-t-want-to-know-about-try-catch-finally-fault-filter.aspx

2 голосов
/ 20 мая 2009

Чтобы добавить больше в смесь, рассмотрите это:

using System;
namespace OddThrow
{
    class Program
    {
        static void Main()
        {
            AppDomain.CurrentDomain.UnhandledException +=
                delegate(object sender, UnhandledExceptionEventArgs e)
            {
                Console.Out.WriteLine("In AppDomain.UnhandledException");
            };
            try
            {
                throw new Exception("Exception!");
            }
            catch
            {
                Console.Error.WriteLine("In catch");
                throw;
            }
            finally
            {
                Console.Error.WriteLine("In finally");
            }
        }
    }
}

Который в моей системе (норвежский) показывает это:

[C:\..] ConsoleApplication5.exe
In catch
In AppDomain.UnhandledException

Ubehandlet unntak: System.Exception: Exception!
   ved OddThrow.Program.Main() i ..\Program.cs:linje 24
In finally
2 голосов
/ 20 мая 2009

Выходные данные фактически получены из обработчика исключений CLR по умолчанию. Обработчики исключений появляются перед блоком finally. После блока finally CLR завершается из-за необработанного исключения (оно не может завершиться раньше, поскольку c # гарантирует [1], что предложение finally вызывается).

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

[1] гарантируется при нормальной работе, по крайней мере, при отсутствии внутренних ошибок во время работы или отключении питания

2 голосов
/ 20 мая 2009

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

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

1 голос
/ 20 мая 2009

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

Я скорректировал ваш образец:

public static void Main()
{
    try
    {
        Console.WriteLine("Before throwing");
        throw new Exception("Exception!");
    }
    finally
    {
        Console.WriteLine("In finally");
        Console.ReadLine();
    }
}

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

0 голосов
/ 20 мая 2009

Следующая попытка:

  1. Я полагаю, что этот случай не упоминается в стандарте c #, и я согласен, что он почти противоречит этому.

  2. Я считаю, что внутренняя причина, по которой это происходит, выглядит примерно так: CLR регистрирует свой обработчик исключений по умолчанию как обработчик SEH в FS: [0] когда в вашем коде больше ловушек, эти обработчики добавляются в цепочку SEH. Альтернативно, только обработчик CLR вызывается во время обработки SEH и обрабатывает цепочку исключений CLR внутри, я не знаю, какой.

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

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

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

На мой взгляд, способ, которым CLR обрабатывает необработанное исключение, не рассматривается в стандарте c #, а только порядок, в котором finals вызывается при развертывании стека. Этот порядок сохраняется. После этого «Воздействие такого завершения определяется реализацией». пункт вступает в силу.

0 голосов
/ 20 мая 2009

Чтобы сложить пару ответов, получается, что как только у вас возникнет необработанное исключение, на AppDomain будет возбуждено событие UnhandledExceptionEvent, а затем код продолжит выполняться (то есть, наконец). Это статья MSDN о событии

0 голосов
/ 20 мая 2009

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

Также помните, что вывод к стандартной ошибке и стандартному выходу буферизован.

0 голосов
/ 20 мая 2009

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

Каждый раз, когда я запускал его, ОС создавала сообщение об ошибке. Таким образом, проблема не в C #, а в том, что ошибка, которая не обрабатывается кодом пользователя, больше не находится под контролем приложения , и поэтому среда выполнения (я полагаю) не может форсировать шаблон выполнения в теме.

Если бы вы создали приложение формы Windows и записали все свои сообщения в текстовое поле (а затем сразу же сбросили их) вместо прямой записи в консоль, вы бы вообще не увидели это сообщение об ошибке, потому что оно было вставлено в консоль ошибок вызывающим приложением, а не собственным кодом.

РЕДАКТИРОВАТЬ

Я постараюсь выделить ключевую часть этого. Необработанные исключения находятся вне вашего контроля, и вы не можете определить, когда будет выполнен их обработчик исключений. Если вы поймаете исключение в какой-то момент в вашем приложении, тогда блоки finally будет выполняться перед блоком catch, находящимся ниже в стеке.

...