Сработает ли код в операторе «Наконец», если я верну значение в блоке «Проба»?


237

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

Пример:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}


Быть обработанным здесь означает: пойман. Даже пустой блок catch в вашем глобальном обработчике достаточно. Кроме того , есть исключения , которые не могут быть обработаны: StackOverflowException, ExecutionEngineExceptionнекоторые из них. И так как они не могут быть обработаны, finallyне будет работать.
Авель

8
@Abel: Вы, кажется, говорите о другой ситуации. Этот вопрос о возвращении в tryблоке. Ничего о резких программах не прерывается.
Джон Скит

6
@Abel: Я не уверен, что вы подразумеваете под «возвращение после исключения», но, похоже, это не то, о чем здесь спрашивают. Посмотрите на код - первое утверждение tryблока - это returnутверждение. (Второе утверждение этого блока недостижимо и выдаст предупреждение.)
Джон Скит

4
@Abel: Действительно, и если бы вопрос был «Будет ли код в операторе finally всегда выполняться в любой ситуации», это было бы актуально. Но это не то, что просили.
Джон Скит

Ответы:


265

Простой ответ: да.


9
@Edi: Хм, я не вижу, какое отношение к нему имеют фоновые потоки. По сути, если весь процесс не прервется, finallyблок будет выполнен.
Джон Скит

3
Если дать длинный ответ, вам не обязательно будет запускать блок finally, если произошло что-то катастрофическое, например, переполнение стека, исключение нехватки памяти, какой-то серьезный сбой или если кто-то отключит вашу машину в нужное время , Но для всех намерений и целей, если вы не делаете что-то очень-очень неправильное, блок finally всегда срабатывает.
Эндрю Роллингс

Если код перед попыткой завершается неудачей по какой-то причине, я заметил, что, наконец, все еще выполняется. В этом случае вы можете добавить условие для finally, которое удовлетворяет критериям для выполнения finally только при успешном выполнении кода under
kjosh

200

Обычно да. Секция finally гарантированно выполнит все, что происходит, включая исключения или оператор возврата. Исключением из этого правила является асинхронное исключение, происходящее в потоке ( OutOfMemoryException, StackOverflowException).

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


154

Вот небольшой тест:

class Class1
{
    [STAThread]
    static void Main(string[] args)
    {
        Console.WriteLine("before");
        Console.WriteLine(test());
        Console.WriteLine("after");
    }

    static string test()
    {
        try
        {
            return "return";
        }
        finally
        {
            Console.WriteLine("finally");
        }
    }
}

Результат:

before
finally
return
after

10
@CodeBlend: это связано с тем, когда WriteLineфактически выполняется результат для вызова метода. В этом случае returnвызов устанавливает результат метода, наконец записывает в консоль - до выхода из метода. Затем WriteLineметод Main выплевывает текст из обратного вызова.
NotMe

38

Цитирование из MSDN

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


3
Ну, в дополнение к исключительным случаям Mehrdad, наконец, также не будет вызываться, если вы отлаживаете в блоке try, а затем прекращаете отладку :). Кажется, в жизни ничего не гарантировано.
StuartLC

1
Не совсем, цитата из MSDN : Однако, если исключение не обработано, выполнение блока finally зависит от того, как инициируется операция по отмене исключения. Это, в свою очередь, зависит от того, как настроен ваш компьютер. ,
Авель

1
@Abel делает решающий момент здесь. Каждый может видеть, как блок finally не выполняется, если, например, программа прерывается через диспетчер задач. Но этот ответ, finallyв сущности , неверен: фактически он ничего не гарантирует даже для совершенно обычных программ (которые, как оказалось, выдали это исключение).
Питер - Восстановить Монику

19

Вообще-то да, наконец-то побежит.

В следующих трех сценариях всегда будет выполняться команда finally :

  1. Никаких исключений не происходит
  2. Синхронные исключения (исключения, возникающие в ходе обычной программы).
    Это включает CLS-совместимые исключения, которые происходят из System.Exception и не-CLS-совместимых исключений, которые не происходят из System.Exception. Исключения, не совместимые с CLS, автоматически включаются в исключение RuntimeWrappedException. C # не может генерировать исключения, не относящиеся к CLS, но такие языки, как C ++, могут. C # может вызывать код, написанный на языке, который может генерировать исключения, не совместимые с CLS.
  3. Асинхронное исключение ThreadAbortException
    Начиная с версии .NET 2.0 исключение ThreadAbortException больше не будет препятствовать запуску finally. ThreadAbortException теперь поднимается до или после finally. Команда finally всегда будет выполняться и не будет прервана прерыванием потока, если попытка действительно была введена до прерывания потока.

В следующем сценарии, наконец, не будет работать:

Асинхронное исключение StackOverflowException.
Начиная с .NET 2.0 переполнение стека приведет к завершению процесса. Команда finally не будет запущена, если не будет применено дополнительное ограничение, чтобы окончательно сделать CER (область ограниченного выполнения). CER не должны использоваться в общем коде пользователя. Их следует использовать только в тех случаях, когда крайне важно, чтобы код очистки всегда выполнялся - после того, как весь процесс завершится из-за переполнения стека, и поэтому все управляемые объекты будут очищены по умолчанию. Таким образом, единственное место, в котором CER должен быть релевантным, - это ресурсы, которые выделены вне процесса, например, неуправляемые дескрипторы.

Как правило, неуправляемый код переносится каким-либо управляемым классом перед использованием пользовательским кодом. Управляемый класс-оболочка обычно использует SafeHandle для переноса неуправляемого дескриптора. SafeHandle реализует критический финализатор и метод Release, который запускается в CER, чтобы гарантировать выполнение кода очистки. По этой причине вы не должны видеть, что CER замусорены из-за кода пользователя.

Поэтому тот факт, что finally не запускается в StackOverflowException, не должен влиять на пользовательский код, так как процесс все равно будет завершен. Если у вас есть крайний случай, когда вам нужно очистить какой-то неуправляемый ресурс вне SafeHandle или CriticalFinalizerObject, то используйте CER следующим образом; но, пожалуйста, обратите внимание, что это плохая практика - неуправляемая концепция должна быть абстрагирована от управляемого класса (-ов) и соответствующих SafeHandle (-ов) по своему замыслу.

например,

// No code can appear after this line, before the try
RuntimeHelpers.PrepareConstrainedRegions();
try
{ 
    // This is *NOT* a CER
}
finally
{
    // This is a CER; guaranteed to run, if the try was entered, 
    // even if a StackOverflowException occurs.
}

Обратите внимание, что даже CER не будет работать в случае SOE. В то время, когда вы это написали, документация MSDN по CER была неправильной / неполной. SOE запустит FailFastвнутренне. Единственный способ, которым мне удалось их поймать, - это настроить хостинг CLR . Обратите внимание, что ваша точка все еще действительна для некоторых других асинхронных исключений.
Авель

10

Есть очень важное исключение из этого, о котором я не упоминал ни в каких других ответах, и которое (после программирования на C # в течение 18 лет) я не могу поверить, что не знал.

Если вы бросаете или запускаете исключение любого рода внутри вашего catchблока (не только странно StackOverflowExceptionsи тому подобное), и у вас нет всего try/catch/finallyблока внутри другого try/catchблока, ваш finallyблок не будет выполняться. Это легко продемонстрировать - и если бы я сам этого не видел, учитывая, как часто я читал, что это действительно странные, крошечные угловые случаи, которые могут привести к тому, что finallyблок не будет выполнен, я бы не поверил.

static void Main(string[] args)
{
    Console.WriteLine("Beginning demo of how finally clause doesn't get executed");
    try
    {
        Console.WriteLine("Inside try but before exception.");
        throw new Exception("Exception #1");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Inside catch for the exception '{ex.Message}' (before throwing another exception).");
        throw;
    }
    finally
    {
        Console.WriteLine("This never gets executed, and that seems very, very wrong.");
    }

    Console.WriteLine("This never gets executed, but I wasn't expecting it to."); 
    Console.ReadLine();
}

Я уверен, что есть причина для этого, но странно, что это не так широко известно. (Это отмечено здесь, например, но нигде в этом конкретном вопросе.)


Ну, finallyблок просто не является уловкой для catchблока.
Питер - Восстановить Монику

7

Я понимаю, что опаздываю на вечеринку, но в сценарии (отличающемся от примера ОП), где действительно исключение выдается состояниями MSDN ( https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx ): «Если исключение не обнаружено, выполнение блока finally зависит от того, выберет ли операционная система запуск операции исключения».

Блок finally гарантированно будет выполняться только в том случае, если какая-либо другая функция (например, Main), находящаяся далее в стеке вызовов, перехватывает исключение. Эта деталь обычно не является проблемой, поскольку все программы C # во время выполнения (CLR и OS) работают на большинстве ресурсов, которыми владеет процесс при выходе (дескрипторы файлов и т. Д.). В некоторых случаях это может быть важно, хотя: Половина выполняемой операции с базой данных, которую вы хотите зафиксировать, соответственно. размотать; или какое-либо удаленное соединение, которое не может быть автоматически закрыто ОС и затем блокирует сервер.


3

Да. Это на самом деле главный пункт окончательного утверждения. Если не произойдет что-либо, вызывающее катафрофизм (нехватка памяти, отключение компьютера и т. Д.), Всегда должен выполняться оператор finally.


1
Я прошу не согласиться. Ср мой ответ. Совершенно нормальное исключение в блоке try достаточно для обхода finallyблока, если это исключение никогда не перехватывается. Это застало меня врасплох ;-).
Питер - Восстановить Монику

@ PeterA.Schneider - Это интересно. Конечно, последствия этого не сильно меняются. Если в стеке вызовов ничего не будет поймано исключение, то это должно означать (если я правильно понимаю), что процесс собирается завершиться. Таким образом, это похоже на ситуацию «тянуть-за-вилкой». Я предполагаю, что вывод из этого заключается в том, что у вас всегда должен быть улов верхнего уровня или вылов необработанного исключения.
Джеффри Л Уитледж

Именно это я тоже понимаю. Я тоже нашел это удивительным. True: наиболее распространенные ресурсы будут освобождаться автоматически, в частности память и открытые файлы. Но ресурсы, о которых ОС не знает - например, откройте соединения с сервером или базой данных - могут оставаться открытыми на удаленной стороне, потому что они никогда не отключались должным образом; сокеты продолжают задерживаться и т. д. Я полагаю, разумно иметь обработчик исключений верхнего уровня, да.
Питер - Восстановить Монику


2

наконец, не запустится, если вы выходите из приложения, используя System.exit (0); как в

try
{
    System.out.println("try");
    System.exit(0);
}
finally
{
   System.out.println("finally");
}

результат будет просто: try


4
Вы отвечаете не на том языке, я полагаю, вопрос был о c#, но это, кажется, так Java. И кроме того, в большинстве случаев System.exit()это намек на плохой дизайн :-)
z00l

0

В 99% случаев будет гарантировано, что код внутри finallyблока будет выполняться, однако подумайте о следующем сценарии: у вас есть поток с блоком try-> finally(нет catch), и вы получаете необработанное исключение в этом потоке. В этом случае поток завершится, и его finallyблок не будет выполнен (в этом случае приложение может продолжить работу).

Этот сценарий довольно редок, но он только показывает, что ответом не всегда является «Да», чаще всего «Да», а иногда, в редких случаях, «Нет».


0

Основная цель блока finally - выполнить все, что написано внутри него. Это не должно зависеть от того, что происходит в try или catch. Однако с System.Environment.Exit (1) приложение будет закрываться без перехода к следующей строке кода.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.