Czy kod w instrukcji Last zostanie uruchomiony, jeśli zwrócę wartość w bloku Try?

237

Sprawdzam jakiś kod dla znajomego i mówię, że używał instrukcji return wewnątrz bloku try-wreszcie. Czy kod w sekcji Wreszcie nadal jest uruchamiany, mimo że reszta bloku try nie?

Przykład:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}
JamesEggers
źródło
Bycie potraktowanym tutaj oznacza: złapany. Wystarczy nawet pusty blok catch w globalnym module obsługi. Ponadto, istnieją wyjątki, które nie mogą być obsługiwane: StackOverflowException, ExecutionEngineExceptionto tylko niektóre z nich. A ponieważ nie można ich obsłużyć, finallynie będzie działać.
Abel
8
@Abel: Wygląda na to, że mówisz o innej sytuacji. To pytanie dotyczy powrotu w trybloku. Nic o nagłym przerwaniu programu.
Jon Skeet
6
@Abel: Nie jestem pewien, co rozumiesz przez „powrót po wyjątku”, ale nie wydaje się, aby o to tutaj pytano. Spójrz na kod - pierwsza instrukcja trybloku jest returninstrukcją. (Druga instrukcja tego bloku jest nieosiągalna i wygeneruje ostrzeżenie.)
Jon Skeet
4
@Abel: Rzeczywiście, i jeśli pytanie brzmiałoby: „Kod w instrukcji ostatecznie będzie zawsze wykonywany w każdej sytuacji”, to byłoby to właściwe. Ale nie o to pytano.
Jon Skeet

Odpowiedzi:

265

Prosta odpowiedź: tak.

Andrew Rollings
źródło
9
@Edi: Nie rozumiem, co mają z tym wspólnego wątki w tle. Zasadniczo, chyba że cały proces zostanie przerwany, finallyblok zostanie wykonany.
Jon Skeet
3
Długa odpowiedź brzmi: nie zawsze trzeba uruchomić blok, jeśli wydarzy się coś katastrofalnego, na przykład przepełnienie stosu, wyjątek braku pamięci, pewnego rodzaju poważna awaria lub jeśli ktoś wyciągnie wtyczkę z komputera we właściwym czasie . Ale dla wszystkich celów i celów, chyba że zrobisz coś bardzo złego, ostatecznie blok zawsze będzie strzelał.
Andrew Rollings,
Jeśli kod przed próbą nie powiedzie się z jakiegoś powodu zauważyłem, że w końcu nadal się wykonuje. W takim przypadku możesz dodać warunek w końcu, który spełnia kryteria do wykonania w końcu tylko wtedy, gdy kod poniżej zostanie pomyślnie
wykonany
200

Zwykle tak. W końcu sekcja ma gwarancję wykonania wszystkiego, co się wydarzy, w tym wyjątków lub instrukcji return. Wyjątkiem od tej reguły jest asynchroniczny wyjątek występujący w wątku ( OutOfMemoryException, StackOverflowException).

Aby dowiedzieć się więcej o wyjątkach asynchronicznych i niezawodnym kodzie w takich sytuacjach, przeczytaj o ograniczonych regionach wykonywania .

Mehrdad Afshari
źródło
154

Oto mały test:

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");
        }
    }
}

Wynik to:

before
finally
return
after
Jon B.
źródło
10
@CodeBlend: Ma to związek z WriteLinefaktycznym wykonaniem wywołania metody. W tym przypadku returnwywołanie ustawia wynik metod, w końcu zapisuje na konsoli - zanim metoda zakończy działanie. Następnie WriteLinemetoda Main wyrzuca tekst z wywołania zwrotnego.
NotMe
38

Cytowanie z MSDN

wreszcie służy do zagwarantowania, że ​​blok instrukcji zostanie wykonany niezależnie od tego, w jaki sposób poprzedni blok try został zamknięty .

Perpetualcoder
źródło
3
Cóż, oprócz wyjątkowych przypadków Mehrdada, w końcu również nie zostanie wywołany, jeśli debugujesz w bloku try, a następnie przestaniesz debugować :). Wydaje się, że nic w życiu nie jest gwarantowane.
StuartLC,
1
Nie całkiem, cytując z MSDN : Jeśli jednak wyjątek nie zostanie obsłużony, wykonanie bloku w końcu zależy od tego, jak wyzwalana jest operacja odwijania wyjątku. To z kolei zależy od konfiguracji komputera. .
Abel
1
@Abel ma tutaj kluczowe znaczenie. Każdy może zobaczyć, jak ostatecznie blok nie jest wykonywany, jeśli np. Program zostanie przerwany przez menedżera zadań. Ale ta odpowiedź jest, jak się wydaje, po prostu błędna: finallyw rzeczywistości nic nie gwarantuje nawet dla całkowicie zwyczajnych programów (które zdarzyły się rzucić ten wyjątek).
Peter - Przywróć Monikę
19

Ogólnie tak, w końcu będzie działać.

W przypadku następujących trzech scenariuszy w końcu ZAWSZE uruchomi się:

  1. Nie występują wyjątki
  2. Wyjątki synchroniczne (wyjątki występujące w normalnym przebiegu programu).
    Obejmuje to wyjątki zgodne z CLS, które pochodzą z System.Exception i wyjątki niezgodne z CLS, które nie pochodzą z System.Exception. Wyjątki niezgodne z CLS są automatycznie pakowane przez wyjątek RuntimeWrappedException. C # nie może zgłaszać wyjątków skarg innych niż CLS, ale języki takie jak C ++ mogą. C # może wywoływać kod napisany w języku, który może zgłaszać wyjątki niezgodne z CLS.
  3. Asynchroniczny wyjątek ThreadAbortException
    Począwszy od platformy .NET 2.0 wyjątek ThreadAbortException nie będzie już uniemożliwiać ostatecznego uruchomienia. ThreadAbortException jest teraz podnoszony przed lub po końcu. Ostatecznie zawsze będzie działać i nie zostanie przerwane przerwaniem wątku, o ile próba została faktycznie podjęta przed przerwaniem wątku.

Następujący scenariusz ostatecznie nie uruchomi się:

Asynchroniczny wyjątek StackOverflowException.
Od wersji .NET 2.0 przepełnienie stosu spowoduje zakończenie procesu. Ostatecznie nie zostanie uruchomione, chyba że zastosowane zostanie dalsze ograniczenie, aby ostatecznie utworzyć CER (region ograniczonego wykonywania). CER nie powinny być używane w ogólnym kodzie użytkownika. Powinny być używane tylko tam, gdzie krytyczne jest, aby zawsze działał kod czyszczący - po tym wszystkim proces i tak zostanie zamknięty z powodu przepełnienia stosu, a zatem wszystkie zarządzane obiekty będą domyślnie czyszczone. Dlatego jedynym miejscem, w którym powinna być odpowiednia CER, są zasoby przydzielone poza procesem, np. Niezarządzane uchwyty.

Zazwyczaj niezarządzany kod jest pakowany przez niektóre klasy zarządzane, zanim zostanie wykorzystany przez kod użytkownika. Zarządzana klasa opakowania zwykle korzysta z SafeHandle do zawijania niezarządzanego uchwytu. SafeHandle implementuje krytyczny finalizator i metodę Release, która jest uruchamiana w CER, aby zagwarantować wykonanie kodu czyszczenia. Z tego powodu nie powinieneś widzieć CER zaśmieconych przez cały kod użytkownika.

Zatem fakt, że w końcu nie działa na StackOverflowException, nie powinien mieć wpływu na kod użytkownika, ponieważ proces i tak zostanie zakończony. Jeśli masz przypadek na krawędzi, w którym musisz wyczyścić niektóre niezarządzane zasoby, poza SafeHandle lub CriticalFinalizerObject, użyj CER w następujący sposób; ale należy pamiętać, że jest to zła praktyka - niezarządzana koncepcja powinna zostać wyodrębniona do klas zarządzanych i odpowiednich SafeHandle z założenia.

na przykład,

// 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.
}
markn
źródło
Należy pamiętać, że nawet CER nie uruchomi się w przypadku SOE. W momencie pisania tego dokumentu dokumentacja MSDN dotycząca CER była niepoprawna / niekompletna. SOE uruchomi FailFastwewnętrznie. Jedynym sposobem, w jaki udało mi się je złapać, jest dostosowanie środowiska uruchomieniowego CLR . Zauważ, że twój punkt jest nadal ważny dla niektórych innych wyjątków asynchronicznych.
Abel
10

Jest bardzo ważny wyjątek od tego, o którym nie wspomniałem w żadnej innej odpowiedzi, i który (po programowaniu w C # przez 18 lat) nie mogę uwierzyć, że nie wiedziałem.

Jeśli rzucisz lub wywołać wyjątek dowolny w swoim catchbloku (nie tylko dziwnym StackOverflowExceptionsi tym podobnym) i nie masz całego try/catch/finallybloku w innym try/catchbloku, twój finallyblok się nie wykona. Łatwo to wykazać - i gdybym sam tego nie widział, biorąc pod uwagę, jak często czytałem, że to naprawdę dziwne, małe, narożne przypadki, które mogą spowodować, że finallyblok nie zostanie wykonany, nie uwierzyłbym.

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();
}

Jestem pewien, że istnieje ku temu powód, ale to dziwne, że nie jest szerzej znany. (Zostało to odnotowane na przykład tutaj , ale nigdzie w tym konkretnym pytaniu).

Ken Smith
źródło
Cóż, finallyblok po prostu nie jest catchblokiem dla bloku.
Peter - Przywróć Monikę
7

Zdaję sobie sprawę, że jestem spóźniony na imprezę, ale w scenariuszu (różniącym się od przykładu OP), w którym rzeczywiście zgłaszane są wyjątki stany MSDN ( https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx ): „Jeśli wyjątek nie zostanie przechwycony, wykonanie bloku w końcu zależy od tego, czy system operacyjny zdecyduje się uruchomić operację cofania wyjątku.”

Ostatecznie blok ma gwarancję wykonania tylko wtedy, gdy jakaś inna funkcja (taka jak Main) w górę stosu wywołań wyjątku. Ten szczegół zwykle nie stanowi problemu, ponieważ wszystkie środowiska wykonawcze (CLR i OS) programy C # działają na wolnych zasobach, które większość procesów posiada po wyjściu (uchwyty plików itp.). W niektórych przypadkach może to być kluczowe: Operacja bazy danych w połowie, którą chcesz zatwierdzić. rozwijać; lub niektóre zdalne połączenie, które może nie zostać automatycznie zamknięte przez system operacyjny, a następnie zablokuje serwer.

Peter - przywróć Monikę
źródło
3

Tak. To jest właśnie główny punkt ostatecznego stwierdzenia. O ile nie wydarzy się coś katastroficznego (brak pamięci, komputer niepodłączony itp.), Należy zawsze wykonać instrukcję w końcu.

Jeffrey L. Whitledge
źródło
1
Błagam się nie zgodzić. Por. moja odpowiedź. Całkowicie normalny wyjątek w bloku try wystarcza na ominięcie finallybloku, jeśli wyjątek ten nigdy nie zostanie złapany. Zaskoczyło mnie to ;-).
Peter - Przywróć Monikę
@ PeterA.Schneider - To ciekawe. Oczywiście implikacje tego tak naprawdę niewiele się zmieniają. Jeśli nic na stosie wywołań nie złapie wyjątku, to powinno to oznaczać (jeśli dobrze rozumiem), że proces wkrótce się zakończy. Jest to więc podobne do sytuacji ciągnięcia za wtyczkę. Wydaje mi się, że z tego wynika, że ​​zawsze powinieneś mieć haczyk najwyższego poziomu lub nieobsługiwany wyjątek.
Jeffrey L. Whitledge
Dokładnie to też rozumiem. To też mnie zaskoczyło. To prawda: najpowszechniejsze zasoby zostaną zwolnione automatycznie, w szczególności pamięć i otwarte pliki. Ale zasoby, o których system operacyjny nie wie - na przykład otwarte połączenia z serwerem lub bazą danych - mogą pozostać otwarte po stronie zdalnej, ponieważ nigdy nie zostały poprawnie zamknięte; gniazda wciąż się utrzymują itp. Wydaje mi się, że rozsądnie jest mieć najwyższy poziom obsługi wyjątków, tak.
Peter - Przywróć Monikę
2

w końcu nie będzie działać, jeśli wychodzisz z aplikacji przy użyciu System.exit (0); jak w

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

wynik byłby po prostu: spróbuj

Hakuna Matata
źródło
4
Odpowiadasz w niewłaściwym języku, jak sądzę, pytanie dotyczyło c#, ale wydaje się, że tak Java. A poza tym w większości przypadków System.exit()jest ślad złego projektu :-)
z00l
0

99% scenariuszy gwarantuje, że kod wewnątrz finallybloku będzie działał, jednak pomyśl o tym scenariuszu: masz wątek, który ma try-> finallyblok (niecatch ) i otrzymujesz nieobsługiwany wyjątek w tym wątku. W takim przypadku wątek zostanie zamknięty, a jego finallyblok nie zostanie wykonany (w tym przypadku aplikacja może nadal działać)

Ten scenariusz jest dość rzadki, ale ma on jedynie na celu pokazanie, że odpowiedź ZAWSZE nie brzmi „tak”, najczęściej jest to odpowiedź „tak”, a czasami w rzadkich przypadkach „nie”.

Jonathan Perry
źródło
0

Głównym celem wreszcie bloku jest wykonanie tego, co jest w nim zapisane. Nie powinno to zależeć od tego, co dzieje się podczas try lub catch. Jednak z System.Environment.Exit (1) aplikacja zakończy działanie bez przejścia do następnego wiersza kodu.

Ashish Sahu
źródło