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


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.