Wird Code in einer finally-Anweisung ausgelöst, wenn ich einen Wert in einem Try-Block zurückgebe?

237

Ich überprüfe einen Code für einen Freund und sage, dass er eine return-Anweisung in einem Try-finally-Block verwendet hat. Wird der Code im Abschnitt "Endlich" immer noch ausgelöst, obwohl der Rest des try-Blocks dies nicht tut?

Beispiel:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}
JamesEggers
quelle
Hier gehandhabt zu werden bedeutet: gefangen. Selbst ein leerer Catch-Block in Ihrem globalen Handler reicht aus. Es gibt auch Ausnahmen, die nicht behandelt werden können : StackOverflowException, ExecutionEngineExceptionsind einige davon. Und da sie nicht gehandhabt werden können, finallylaufen sie nicht.
Abel
8
@Abel: Du scheinst über eine andere Situation zu sprechen. Bei dieser Frage geht es darum , in einem tryBlock zurückzukehren. Nichts über abrupte Programmabbrüche.
Jon Skeet
6
@Abel: Ich bin mir nicht sicher, was du mit "nach einer Ausnahme zurückkehren" meinst, aber das scheint nicht das zu sein, worüber hier gefragt wird. Schauen Sie sich den Code an - die erste Anweisung des tryBlocks ist eine returnAnweisung. (Die zweite Anweisung dieses Blocks ist nicht erreichbar und generiert eine Warnung.)
Jon Skeet
4
@Abel: In der Tat, und wenn die Frage gewesen wäre "Wird Code in einer finally-Anweisung immer in jeder Situation ausgeführt", wäre dies relevant gewesen. Aber das wurde nicht gefragt.
Jon Skeet

Antworten:

265

Einfache Antwort: Ja.

Andrew Rollings
quelle
9
@Edi: Ähm, ich sehe nicht, was Hintergrund-Threads damit zu tun haben. Grundsätzlich wird der finallyBlock ausgeführt , es sei denn, der gesamte Prozess wird abgebrochen .
Jon Skeet
3
Die lange Antwort lautet, dass Sie nicht unbedingt einen endgültigen Block zum Ausführen erhalten, wenn etwas Katastrophales passiert ist, z. B. ein Stapelüberlauf, eine Ausnahme wegen Speichermangel, ein harter Absturz oder wenn jemand Ihren Computer zum richtigen Zeitpunkt vom Stromnetz trennt . Aber in jeder Hinsicht wird der finally-Block immer ausgelöst, es sei denn, Sie tun etwas sehr, sehr Falsches.
Andrew Rollings
Wenn der Code vor dem Versuch aus irgendeinem Grund fehlschlägt, ist mir aufgefallen, dass er schließlich noch ausgeführt wird. In diesem Fall können Sie eine Bedingung für in finally hinzufügen, die die Kriterien für die endgültige Ausführung nur dann erfüllt, wenn der Code unter erfolgreich ausgeführt wird
kjosh
200

Normalerweise ja. Es wird garantiert, dass der Abschnitt finally ausgeführt wird, was auch immer passiert, einschließlich Ausnahmen oder return-Anweisungen. Eine Ausnahme von dieser Regel ist eine asynchrone Ausnahme, die im Thread ( OutOfMemoryException, StackOverflowException) auftritt .

Weitere Informationen zu asynchronen Ausnahmen und zuverlässigem Code in diesen Situationen finden Sie in den eingeschränkten Ausführungsbereichen .

Mehrdad Afshari
quelle
154

Hier ist ein kleiner 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");
        }
    }
}

Das Ergebnis ist:

before
finally
return
after
Jon B.
quelle
10
@CodeBlend: Es hat damit zu tun, wann das WriteLineErgebnis des Methodenaufrufs tatsächlich ausgeführt wird. In diesem Fall returnlegt der Aufruf das Methodenergebnis fest und schreibt schließlich in die Konsole - bevor die Methode beendet wird. Dann WriteLinespuckt die in der Main-Methode den Text aus dem Rückruf aus.
NotMe
38

Zitieren aus MSDN

schließlich wird verwendet , um einen Anweisungsblock Code ausgeführt zu gewährleisten , unabhängig davon , wie der vorhergehende Versuch Block verlassen .

Perpetualcoder
quelle
3
Nun, zusätzlich zu Mehrdads Ausnahmefällen wird schließlich auch nicht aufgerufen, wenn Sie in einem Try-Block debuggen und dann das Debuggen beenden :). Nichts im Leben ist garantiert, wie es scheint.
StuartLC
1
Nicht ganz, Zitat aus MSDN : Wenn die Ausnahme jedoch nicht behandelt wird, hängt die Ausführung des finally-Blocks davon ab, wie die Ausnahme-Abwicklungsoperation ausgelöst wird. Dies hängt wiederum davon ab, wie Ihr Computer eingerichtet ist. .
Abel
1
@Abel macht hier einen entscheidenden Punkt. Jeder kann sehen, wie ein finally-Block nicht ausgeführt wird, wenn beispielsweise das Programm über den Task-Manager abgebrochen wird. Aber diese Antwort ist finallyin der jetzigen Form einfach falsch: Sie garantiert sogar nichts für ganz normale Programme (die diese Ausnahme ausgelöst haben).
Peter - Monica
19

Generell ja, das läuft endlich.

Für die folgenden drei Szenarien wird das endlich IMMER ausgeführt:

  1. Es treten keine Ausnahmen auf
  2. Synchrone Ausnahmen (Ausnahmen, die im normalen Programmablauf auftreten).
    Dies umfasst CLS-kompatible Ausnahmen, die von System.Exception abgeleitet sind, und nicht CLS-kompatible Ausnahmen, die nicht von System.Exception abgeleitet sind. Nicht CLS-kompatible Ausnahmen werden automatisch von der RuntimeWrappedException umbrochen. C # kann keine Nicht-CLS-Beschwerdeausnahmen auslösen, Sprachen wie C ++ jedoch. C # ruft möglicherweise Code auf, der in einer Sprache geschrieben ist, die nicht CLS-kompatible Ausnahmen auslösen kann.
  3. Asynchrone ThreadAbortException
    Ab .NET 2.0 verhindert eine ThreadAbortException nicht mehr, dass eine endgültige Ausführung ausgeführt wird. ThreadAbortException wird jetzt vor oder nach dem endgültigen angehoben. Das endgültige wird immer ausgeführt und nicht durch einen Thread-Abbruch unterbrochen, solange der Versuch tatsächlich eingegeben wurde, bevor der Thread-Abbruch erfolgte.

Das folgende Szenario wird schließlich nicht ausgeführt:

Asynchrone StackOverflowException.
Ab .NET 2.0 wird der Prozess durch einen Stapelüberlauf beendet. Das finally wird nicht ausgeführt, es sei denn, eine weitere Einschränkung wird angewendet, um das finally zu einer CER (Constrained Execution Region) zu machen. CERs sollten nicht im allgemeinen Benutzercode verwendet werden. Sie sollten nur verwendet werden, wenn es wichtig ist, dass der Bereinigungscode immer ausgeführt wird - nachdem der gesamte Prozess ohnehin beim Stapelüberlauf heruntergefahren wurde und alle verwalteten Objekte daher standardmäßig bereinigt werden. Daher sollte eine CER nur für Ressourcen relevant sein, die außerhalb des Prozesses zugewiesen werden, z. B. nicht verwaltete Handles.

In der Regel wird nicht verwalteter Code von einer verwalteten Klasse umbrochen, bevor er vom Benutzercode verwendet wird. Die verwaltete Wrapper-Klasse verwendet normalerweise ein SafeHandle, um das nicht verwaltete Handle zu verpacken. Der SafeHandle implementiert einen kritischen Finalizer und eine Release-Methode, die in einer CER ausgeführt wird, um die Ausführung des Bereinigungscodes zu gewährleisten. Aus diesem Grund sollten Sie keine CERs im gesamten Benutzercode sehen.

Die Tatsache, dass die Funktion schließlich nicht auf StackOverflowException ausgeführt wird, sollte sich nicht auf den Benutzercode auswirken, da der Prozess ohnehin beendet wird. Wenn Sie einen Randfall haben, in dem Sie eine nicht verwaltete Ressource außerhalb eines SafeHandle- oder CriticalFinalizerObject bereinigen müssen, verwenden Sie eine CER wie folgt. Beachten Sie jedoch, dass dies eine schlechte Praxis ist. Das nicht verwaltete Konzept sollte von Natur aus in eine verwaltete Klasse und geeignete SafeHandle (s) abstrahiert werden.

z.B,

// 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
quelle
Bitte beachten Sie, dass bei einem SOE nicht einmal eine CER ausgeführt wird. Zu dem Zeitpunkt, als Sie dies geschrieben haben, war die MSDN-Dokumentation zu CER falsch / unvollständig. Ein SOE löst FailFastintern ein aus. Die einzige Möglichkeit, diese zu erfassen, besteht darin, das CLR-Laufzeithosting anzupassen . Beachten Sie, dass Ihr Punkt für einige andere asynchrone Ausnahmen weiterhin gültig ist.
Abel
10

Es gibt eine sehr wichtige Ausnahme, die ich in keiner anderen Antwort erwähnt habe und die ich (nach 18-jähriger Programmierung in C #) nicht glauben kann, dass ich es nicht wusste.

Wenn Sie eine Ausnahme jeglicher Art in Ihrem catchBlock auslösen oder auslösen (nicht nur seltsam StackOverflowExceptionsund solche Dinge) und Sie nicht den gesamten try/catch/finallyBlock in einem anderen try/catchBlock haben, wird Ihr finallyBlock nicht ausgeführt. Dies ist leicht zu demonstrieren - und wenn ich es nicht selbst gesehen finallyhätte, hätte ich es nicht geglaubt, wenn man bedenkt, wie oft ich gelesen habe, dass es nur wirklich seltsame, winzige Eckfälle sind, die dazu führen können, dass ein Block nicht ausgeführt wird.

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

Ich bin sicher, es gibt einen Grund dafür, aber es ist bizarr, dass es nicht bekannter ist. (Es wird hier zum Beispiel notiert , aber nirgendwo in dieser speziellen Frage.)

Ken Smith
quelle
Nun, der finallyBlock ist einfach kein Haken für den catchBlock.
Peter - Monica am
7

Mir ist klar, dass ich zu spät zur Party komme, aber in dem Szenario (das sich vom Beispiel des OP unterscheidet), in dem tatsächlich eine Ausnahme ausgelöst wird, werden MSDN-Zustände ( https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx ) ausgelöst : "Wenn die Ausnahme nicht abgefangen wird, hängt die Ausführung des finally-Blocks davon ab, ob das Betriebssystem eine Ausnahme- Abwicklungsoperation auslöst."

Die Ausführung des finally-Blocks ist nur dann garantiert , wenn eine andere Funktion (z. B. Main) weiter oben im Aufrufstapel die Ausnahme abfängt. Dieses Detail ist normalerweise kein Problem, da alle C # -Programme in Laufzeitumgebungen (CLR und OS) auf den meisten Ressourcen ausgeführt werden, die ein Prozess beim Beenden besitzt (Dateihandles usw.). In einigen Fällen kann es jedoch entscheidend sein: Eine Datenbankoperation, die zur Hälfte ausgeführt wird und die Sie festschreiben möchten. entspannen; oder eine Remoteverbindung, die möglicherweise nicht automatisch vom Betriebssystem geschlossen wird und dann einen Server blockiert.

Peter - Monica wieder einsetzen
quelle
3

Ja. Das ist in der Tat der Hauptpunkt einer endgültigen Aussage. Sofern nicht etwas Katastrophales auftritt (nicht genügend Speicher, nicht angeschlossener Computer usw.), sollte die finally-Anweisung immer ausgeführt werden.

Jeffrey L Whitledge
quelle
1
Ich bitte nicht zuzustimmen. Vgl. meine Antwort. Eine völlig normale Ausnahme im try-Block reicht aus, um den finallyBlock zu umgehen , wenn diese Ausnahme nie abgefangen wird. Das hat mich überrascht ;-).
Peter - Monica
@ PeterA.Schneider - Das ist interessant. Natürlich ändern sich die Auswirkungen davon nicht wirklich. Wenn nichts im Aufrufstapel die Ausnahme abfängt, sollte dies bedeuten (wenn ich das richtig verstehe), dass der Prozess kurz vor dem Abbruch steht. Es ist also ähnlich wie beim Ziehen des Steckers. Ich denke, die Erkenntnis daraus ist, dass Sie immer einen Fang auf höchster Ebene oder mit nicht behandelten Ausnahmen haben sollten.
Jeffrey L Whitledge
Genau das verstehe ich auch. Das fand ich auch überraschend. True: Die am häufigsten verwendeten Ressourcen werden automatisch freigegeben, insbesondere Speicher und geöffnete Dateien. Ressourcen, über die das Betriebssystem nichts weiß - beispielsweise offene Server- oder Datenbankverbindungen - können auf der Remote-Seite offen bleiben, da sie noch nie ordnungsgemäß heruntergefahren wurden. Sockets verweilen usw. Ich denke, es ist ratsam, einen Ausnahmebehandler der obersten Ebene zu haben, ja.
Peter - Reinstate Monica
2

Wird schließlich nicht ausgeführt, wenn Sie die Anwendung mit System.exit (0) verlassen. wie in

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

Das Ergebnis wäre nur: versuchen

Hakuna Matata
quelle
4
Sie antworten in der falschen Sprache, ich nehme an, die Frage war ungefähr c#, aber das scheint zu sein Java. Und außerdem ist in den meisten Fällen System.exit()ein Hinweis auf ein schlechtes Design :-)
z00l
0

In 99% der Szenarien wird garantiert, dass der Code innerhalb des finallyBlocks ausgeführt wird. Denken Sie jedoch an dieses Szenario: Sie haben einen Thread mit einem try-> finallyBlock (nein catch) und Sie erhalten eine nicht behandelte Ausnahme innerhalb dieses Threads. In diesem Fall wird der Thread beendet und sein finallyBlock wird nicht ausgeführt (die Anwendung kann in diesem Fall weiter ausgeführt werden).

Dieses Szenario ist ziemlich selten, aber es soll nur zeigen, dass die Antwort nicht IMMER "Ja" ist, sondern meistens "Ja" und manchmal, in seltenen Fällen, "Nein".

Jonathan Perry
quelle
0

Der Hauptzweck von finally block besteht darin, alles auszuführen, was darin geschrieben ist. Es sollte nicht davon abhängen, was in try oder catch passiert. Mit System.Environment.Exit (1) wird die Anwendung jedoch beendet, ohne zur nächsten Codezeile zu wechseln.

Ashish Sahu
quelle