Если я вернусь из блока try / finally в C #, всегда ли выполняется код в finally?

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


person zorg    schedule 21.04.2012    source источник
comment
Некоторые исключения невозможно отловить, например, переполнение стека.   -  person Michael Gattuso    schedule 21.04.2012
comment
См. Здесь: stackoverflow.com/a/50627/5190   -  person Vinko Vrsalovic    schedule 21.04.2012
comment
Можете ли вы закончить свой сценарий? Имейте в виду, что программа может быть завершена принудительно. Судя по названию, похоже, что вы спрашиваете, попробуйте {return; } finally {// mycode}, наконец, запустит код. Ответа нет.   -  person David Z.    schedule 21.04.2012
comment
@DavidZ .: Вообще-то да. ideone.com/RFZ2M   -  person Ry-♦    schedule 21.04.2012
comment
возможный дубликат Что на самом деле происходит в попробуйте {вернуть х; } наконец {x = null; } оператор?, который не только отвечает на этот вопрос, но, возможно, и на большинство подразумеваемых вопросов, выходящих за рамки OP.   -  person Erik Philips    schedule 21.04.2012
comment
@VinkoVrsalovic: Это не так актуально, потому что return никогда не происходит.   -  person Ry-♦    schedule 21.04.2012
comment
Я поправляюсь :) Приятно знать, каждый день узнавайте что-то еще :)   -  person David Z.    schedule 21.04.2012
comment
@DavidZ. Что ж, если вы читаете ответ Эрика, вы не обязательно ошибаетесь. По крайней мере, теоретически.   -  person Brian    schedule 23.04.2012


Ответы (4)


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

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

Эрик Липперт написал гораздо более исчерпывающий ответ, в котором описаны дополнительные случаи: https://stackoverflow.com/a/10260233/53777

Что касается goto, ответ по-прежнему положительный. Рассмотрим следующий код:

try
{
    Console.WriteLine("Inside the Try");
    goto MyLabel;
}
finally
{
    Console.WriteLine("Inside the Finally");
}

MyLabel:
    Console.WriteLine("After the Label");

Полученный результат таков:

Внутри попытки

Внутри наконец

После этикетки

person Dan Rigby    schedule 21.04.2012

В остальных ответах есть ряд неточностей.

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

Во всех остальных случаях нет гарантии, что код в блоке finally будет вызван. Особенно:

  • Если код блока try переходит в бесконечный цикл или поток замораживается и никогда не размораживается, то код блока finally никогда не вызывается.

  • Если процесс приостановлен в отладчике, а затем агрессивно уничтожен, то блок finally никогда не вызывается. Если процесс работает без сбоев, блок finally никогда не вызывается.

  • Если шнур питания выдернут из стены, блок finally никогда не вызывается.

  • Если возникает исключение без соответствующего блока перехвата, то то, выполняется ли блок finally или нет, является деталью реализации среды выполнения. Среда выполнения может выбрать любое поведение при возникновении неперехваченного исключения. Оба варианта «не запускать блоки finally» и «запускать блоки finally» являются примерами «любого поведения», поэтому можно выбрать любой из них. Обычно среда выполнения спрашивает пользователя, хотят ли они подключить отладчик перед запуском блоков finally; если пользователь говорит «нет», то запускаются блоки finally. Но опять же: среда выполнения не требуется для этого. Это могло просто быстро потерпеть неудачу.

Вы не можете полагаться на то, что блоки finally всегда вызываются. Если вам требуется надежная гарантия выполнения кода, вам не следует писать пробную версию, вы должны написать ограниченная область выполнения. Правильное написание CER - одна из самых сложных задач в программировании на C #, поэтому внимательно изучите документацию, прежде чем пытаться писать код.

Между прочим, "забавный факт" об окончательно заблокированных gotos:

try { goto X; } finally { throw y; } 
X : Console.WriteLine("X");

X - это недостижимая метка, на которую нацелен достижимый goto! Так что в следующий раз, когда вы будете на вечеринке, вы можете подумать: «Привет всем, может ли кто-нибудь создать программу на C # с недостижимой меткой, на которую нацелен достижимый goto?» и вы увидите, кто на вечеринке прочитал спецификацию достижимости, а кто нет!

person Eric Lippert    schedule 21.04.2012
comment
Я бы добавил ошибки: во время выполнения, ОС, драйверов, оборудования и т. Д., Которые вызывали зависания BSOD или ПК. - person Lukasz Madon; 21.04.2012
comment
@EricLippert Если в блоке try возникает исключение outofMemory, я думаю, что наконец не будет выполнено. Правильно ли я предполагаю это? Пожалуйста, дай мне знать. - person Sandeep; 21.04.2012
comment
Итак, мораль этой истории такова: никогда не приглашайте Эрика Липперта на свои вечеринки;) - person Tergiver; 24.04.2012
comment
@Tergiver: Малоизвестный факт: разработчики компиляторов устраивают лучшие вечеринки. - person Eric Lippert; 24.04.2012
comment
Слей это! Меня никогда не приглашают на вечеринки разработчиков компиляторов. - person Tergiver; 24.04.2012
comment
Все те вечеринки по программированию, на которые я хожу ... ну вообще-то никогда =) - person Coops; 27.11.2012
comment
Что такого необычного в unreachable label that is targetted by a reachable goto? Или, может быть, это не кажется чем-то необычным, потому что я никогда не думал об этом до публикации, а теперь, после публикации, мне показали решение, которое простое, но трудно придумать (поэтому я пропустил сложную часть, прочитав сообщение) ? :) - person qqqqqqq; 12.03.2020
comment
@qqqqqqq: потому что люди обычно думают о goto как о мгновенной передаче управления от текущего оператора к целевому оператору. Эта интуиция ложна. Точно так же люди думают о throw как о мгновенной передаче управления от текущего оператора к соответствующему блоку catch, если нет finally, но это тоже ложь. Можете ли вы привести пример программы, в которой есть бросок, соответствующий улов, в конце концов нет никакого вмешательства и управление никогда не попадает в тело блока улова? - person Eric Lippert; 13.03.2020
comment
@EricLippert, я не могу. Не могли бы вы помочь мне в этом? Может быть, в скрипке? :) - person qqqqqqq; 13.03.2020
comment
@qqqqqqq: знаете ли вы, что такое фильтр исключений? Когда он запускается? - person Eric Lippert; 13.03.2020
comment
@EricLippert, о. Очень круто. Никогда даже не слышал об этой функции. Спасибо. Я проверю их. (у) - person qqqqqqq; 13.03.2020
comment
@EricLippert, вы имели в виду что-то похожее на this, когда задавали вопрос о недостижимых catch блоках тело? :) - person qqqqqqq; 13.03.2020
comment
@qqqqqqq: Да, но пошли дальше. Предположим, вместо false у вас есть when M(), где M() - функция, которая переходит в бесконечный цикл. Возникает исключение, фильтр исключений запускается, чтобы узнать, обрабатывает ли его блок catch, но фильтр работает вечно, поэтому мы никогда не попадаем в блок catch. - person Eric Lippert; 13.03.2020
comment
@qqqqqqq: Но вот действительно неприятный. Предположим, у вас есть void X() { try { ObtainAdminPowers(); DoSomethingDangerous(); } finally { ReleaseAdminPowers(); }}, а DoSomethingDangerous выдает исключение. Теперь предположим, что у нас есть try { X(); } catch (Exception) when (M()) { }}. Метод M() запускается до освобождения административных полномочий! Автор X считает, что никакой другой код, кроме DoSomethingDangerous, никогда не сможет использовать полномочия администратора, но автор ошибается! - person Eric Lippert; 13.03.2020
comment
@EricLippert, я считаю, что автор X() должен был определить Y() следующим образом void Y() { try { X(); } catch (Exception) when (M()) { }} вместо того, чтобы выдавать голые X() :) - person qqqqqqq; 13.03.2020
comment
@qqqqqqq: Правильным смягчением в этом случае является смехотворный void X() { try { Obtain(); DoIt(); } catch { Release(); throw; } Release(); }, который не напишет ни один здравомыслящий человек. Это один из прискорбных недостатков конструкции безопасности, присущих исключениям .NET. - person Eric Lippert; 14.03.2020
comment
@EricLippert, я создал комнату. Не могли бы вы присоединиться, пожалуйста? :) - person qqqqqqq; 14.03.2020
comment
@EricLippert: Есть ли причина для запуска M() перед блоком finally, или это была просто ужасная ошибка? Я не могу придумать какой-либо веской причины, чтобы это работало таким образом. - person user2357112 supports Monica; 19.11.2020
comment
@ user2357112supportsMonica: Решение конечно сомнительное; Не знаю, зашел бы я так далеко, чтобы сказать ужасную ошибку. Принцип здесь таков: вам нужно знать, будет ли обнаружено исключение, потому что, если ответ отрицательный, тогда CLR оставляет за собой право делать такие вещи, как активация отладчиков или запись аварийных дампов как можно раньше, то есть до того, как окончательно заблокируется. - person Eric Lippert; 19.11.2020
comment
@ user2357112supportsMonica: Люди понимают, что поток управления запускается блоками finally в порядке от самого последнего к последнему введенному блоку попыток до тех пор, пока вы не найдете подходящую ловушку, но это не то, что происходит. CLR находит соответствующий catch first, и then, если он может быть найден, выполняются блоки finally. Если соответствующий улов не может быть найден, это вызывает неопределенное поведение, и механизмы аварийного восстановления среды CLR вступают во владение. - person Eric Lippert; 19.11.2020
comment
@EricLippert Возможно, основная причина в том, что try { M } finally { N } имеет значение, отличное от { try { M } catch (Exception e) { N; throw; } N }. Я понимаю мотивацию, но не могу не задаться вопросом, можно ли этого достичь без введения полностью отдельного примитива, связанного с исключениями. - person Joker_vD; 21.11.2020

Вот некоторые примеры:

Environment.FailFast ()

        try
        {
            Console.WriteLine("Try");
            Environment.FailFast("Test Fail");

        }
        catch (Exception)
        {
            Console.WriteLine("catch");
        }
        finally
        {
            Console.WriteLine("finally");
        }

На выходе только «Попробуй».

Stackoverflow

        try
        {
            Console.WriteLine("Try");
            Rec();
        }
        catch (Exception)
        {
            Console.WriteLine("catch");
        }
        finally
        {
            Console.WriteLine("finally");
        }

Где Rec:

    private static void Rec()
    {
        Rec();
    }

Результатом будет только «Попытка», и процесс завершится из-за StackOverflow.

Необработанное исключение

        try
        {
            Console.WriteLine("Try");
            throw new Exception();
        }
        finally
        {
            Console.WriteLine("finally");
        }
person Lukasz Madon    schedule 21.04.2012
comment
В последнем примере (Необработанное исключение) фактически выполняется finally блок. - person N. M.; 01.12.2020

В случае фатальных исключений, завершающих работу приложения, блок finally не вызывается. Включает переполнение стека, исключения во время JIT вызываемых методов, фатальные исключения во время выполнения CLR.

Как указывает @mintech, если приложение зависает внутри блока, оно просто не дойдет до блока finally. Это включает ожидание объектов синхронизации, бесконечные циклы взаимоблокировок или даже пользовательский интерфейс, который не имеет возможности закрыть его.

person Alexei Levenkov    schedule 21.04.2012