Was passiert, wenn ein finally-Block eine Ausnahme auslöst?

266

Was passiert genau , wenn ein finally-Block eine Ausnahme auslöst?

Was passiert insbesondere, wenn die Ausnahme in der Mitte eines finally-Blocks ausgelöst wird? Werden die restlichen Anweisungen (nach) in diesem Block aufgerufen?

Mir ist bewusst, dass sich Ausnahmen nach oben ausbreiten werden.

Jack Kada
quelle
8
Warum nicht einfach mal probieren? Aber bei solchen Dingen mag ich am liebsten die Rückkehr vor dem Endlich und dann etwas anderes aus dem Endblock. :)
ANeves
8
Alle Anweisungen in einem finally-Block müssen ausgeführt werden. Es kann keine Rückkehr geben. msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Tim Scarborough

Antworten:

419

Wenn ein finally-Block eine Ausnahme auslöst , was genau passiert dann?

Diese Ausnahme breitet sich immer weiter aus und wird (kann) auf einer höheren Ebene behandelt.

Ihr endgültiger Block wird nicht über den Punkt hinaus abgeschlossen, an dem die Ausnahme ausgelöst wird.

Wenn der finally-Block während der Behandlung einer früheren Ausnahme ausgeführt wurde, geht diese erste Ausnahme verloren.

C # 4-Sprachspezifikation § 8.9.5: Wenn der finally-Block eine weitere Ausnahme auslöst, wird die Verarbeitung der aktuellen Ausnahme beendet.

Henk Holterman
quelle
9
Wenn dies nicht ThreadAbortExceptionder Fall ist , wird der gesamte endgültige Block zuerst beendet, da es sich um einen kritischen Abschnitt handelt.
Dmytro Shevchenko
1
@Shedal - Sie haben Recht, aber das gilt nur für "bestimmte asynchrone Ausnahmen", dh ThreadAbortException. Für normalen 1-Thread-Code gilt meine Antwort.
Henk Holterman
"Erste Ausnahme ist verloren" - das ist eigentlich sehr enttäuschend. Gelegentlich finde ich IDisposable-Objekte, die in Dispose () eine Ausnahme auslösen, was dazu führt, dass die Ausnahme in der Klausel "using" verloren geht.
Alex Burtsev
"Ich finde IDisposable-Objekte, die in Dispose () eine Ausnahme auslösen" - das ist gelinde gesagt seltsam. Lesen Sie weiter MSDN: Vermeiden Sie es, eine Ausnahme aus Dispose (bool) zu werfen, außer unter ...
Henk Holterman
1
@HenkHolterman: Festplattenfüllungsfehler sind auf einer direkt verbundenen primären Festplatte nicht sehr häufig, aber Programme schreiben manchmal Dateien auf Wechseldatenträger oder Netzwerkfestplatten. Probleme können bei diesen viel häufiger auftreten. Wenn jemand einen USB-Stick herauszieht, bevor eine Datei vollständig geschrieben wurde, ist es besser, ihn sofort zu informieren, als zu warten, bis er dahin kommt, wo er hingeht, und festzustellen, dass die Datei beschädigt ist. Dem früheren Fehler nachzugeben, wenn es einen gibt, mag vernünftiges Verhalten sein, aber wenn es keinen früheren Fehler gibt, ist es besser, das Problem zu melden, als es nicht zu melden.
Supercat
101

Bei Fragen wie diesen öffne ich normalerweise ein leeres Konsolenanwendungsprojekt in Visual Studio und schreibe ein kleines Beispielprogramm:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Inner catch block handling {0}.", ex.Message);
                throw;
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

Wenn Sie das Programm ausführen, sehen Sie die genaue Reihenfolge, in der catchund finallyBlöcke ausgeführt werden. Beachten Sie, dass Code im finally-Block nach dem Auslösen der Ausnahme nicht ausgeführt wird (in diesem Beispielprogramm warnt Visual Studio Sie sogar, dass nicht erreichbarer Code erkannt wurde):

Ausnahme beim Behandeln des inneren Fangblocks vom Try-Block ausgelöst.
Inner endlich blockieren
Ausnahme für die Behandlung des äußeren Fangblocks, die vom endgültigen Block ausgelöst wird.
Außen endlich blockieren

Zusätzliche Bemerkung

Wie Michael Damatov betonte, wird eine Ausnahme vom tryBlock "gegessen", wenn Sie sie nicht in einem (inneren) catchBlock behandeln. Tatsächlich wird im obigen Beispiel die erneut ausgelöste Ausnahme nicht im äußeren Catch-Block angezeigt. Um dies noch deutlicher zu machen, schauen Sie sich das folgende leicht modifizierte Beispiel an:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

Wie Sie aus der Ausgabe sehen können, ist die innere Ausnahme "verloren" (dh ignoriert):

Inner endlich blockieren
Ausnahme für die Behandlung des äußeren Fangblocks, die vom endgültigen Block ausgelöst wird.
Außen endlich blockieren
Dirk Vollmar
quelle
2
Da Sie die Ausnahme in Ihren inneren Fang werfen, wird 'Inner finally block' in diesem Beispiel niemals erreicht
Theofanis Pantelides
4
@Theofanis Pantelides: Nein, ein finallyBlock wird (fast) immer ausgeführt, dies gilt auch in diesem Fall für den inneren finally-Block (probieren Sie einfach das Beispielprogramm selbst aus (ein finally-Block wird im Falle eines nicht wiederherstellbaren Blocks nicht ausgeführt Ausnahme, zB eine EngineExecutionException, aber in einem solchen Fall wird Ihr Programm trotzdem sofort beendet).
Dirk Vollmar
1
Ich sehe jedoch nicht, welche Rolle der Wurf beim ersten Fang Ihres ersten Codeteils spielt. Ich habe es mit und ohne mit einer Konsolenanwendung versucht, kein Unterschied gefunden.
JohnPan
@johnpan: Der Punkt war zu zeigen, dass der finally-Block immer ausgeführt wird, auch wenn sowohl try als auch catch block eine Ausnahme auslösen. Es gibt in der Tat keinen Unterschied in der Konsolenausgabe.
Dirk Vollmar
10

Wenn eine Ausnahme aussteht (wenn der tryBlock eine, finallyaber keine hat catch), ersetzt die neue Ausnahme diese.

Wenn keine Ausnahme ansteht, funktioniert dies genauso wie das Auslösen einer Ausnahme außerhalb des finallyBlocks.

Guffa
quelle
Eine Ausnahme könnte auch anhängigen werden , wenn es ist ein passender catchBlock daß (Wieder-) eine Ausnahme ausgelöst.
stakx - nicht mehr beitragen
4

Die Ausnahme wird weitergegeben.

Darin Dimitrov
quelle
2
@bitbonk: wie immer von innen nach außen.
Piskvor verließ das Gebäude
3

Schnelles (und ziemlich offensichtliches) Snippet zum Speichern der "ursprünglichen Ausnahme" (in tryBlock geworfen ) und zum Opfer von "endgültige Ausnahme" (in finallyBlock geworfen ), falls das Original für Sie wichtiger ist:

try
{
    throw new Exception("Original Exception");
}
finally
{
    try
    {
        throw new Exception("Finally Exception");
    }
    catch
    { }
}

Wenn der obige Code ausgeführt wird, überträgt "Original Exception" den Aufrufstapel und "Endlich Exception" geht verloren.

lxa
quelle
2

Ich musste dies tun, um einen Fehler beim Schließen eines Streams zu erkennen, der aufgrund einer Ausnahme nie geöffnet wurde.

errorMessage = string.Empty;

try
{
    byte[] requestBytes = System.Text.Encoding.ASCII.GetBytes(xmlFileContent);

    webRequest = WebRequest.Create(url);
    webRequest.Method = "POST";
    webRequest.ContentType = "text/xml;charset=utf-8";
    webRequest.ContentLength = requestBytes.Length;

    //send the request
    using (var sw = webRequest.GetRequestStream()) 
    {
        sw.Write(requestBytes, 0, requestBytes.Length);
    }

    //get the response
    webResponse = webRequest.GetResponse();
    using (var sr = new StreamReader(webResponse.GetResponseStream()))
    {
        returnVal = sr.ReadToEnd();
        sr.Close();
    }
}
catch (Exception ex)
{
    errorMessage = ex.ToString();
}
finally
{
    try
    {
        if (webRequest.GetRequestStream() != null)
            webRequest.GetRequestStream().Close();
        if (webResponse.GetResponseStream() != null)
            webResponse.GetResponseStream().Close();
    }
    catch (Exception exw)
    {
        errorMessage = exw.ToString();
    }
}

Wenn die webRequest erstellt wurde, aber während des ein Verbindungsfehler aufgetreten ist

using (var sw = webRequest.GetRequestStream())

Dann würde der Endlich eine Ausnahme abfangen, die versucht, Verbindungen zu schließen, die er für offen hielt, weil die webRequest erstellt wurde.

Wenn der Code schließlich keinen Try-Catch enthalten würde, würde dieser Code beim Bereinigen der webRequest eine nicht behandelte Ausnahme verursachen

if (webRequest.GetRequestStream() != null) 

Von dort aus würde der Code beendet, ohne den aufgetretenen Fehler ordnungsgemäß zu behandeln, und daher Probleme für die aufrufende Methode verursachen.

Hoffe das hilft als Beispiel

Emma Grant
quelle
1

Wenn Sie eine Ausnahme auslösen, während eine andere Ausnahme aktiv ist, wird die erste Ausnahme durch die zweite (spätere) Ausnahme ersetzt.

Hier ist ein Code, der veranschaulicht, was passiert:

    public static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("first exception");
            }
            finally
            {
                //try
                {
                    throw new Exception("second exception");
                }
                //catch (Exception)
                {
                    //throw;
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
  • Führen Sie den Code aus und Sie sehen "zweite Ausnahme"
  • Kommentieren Sie die try and catch-Anweisungen aus und Sie sehen "erste Ausnahme".
  • Kommentieren Sie auch den Wurf aus; Anweisung und Sie werden wieder "zweite Ausnahme" sehen.
Doug Coburn
quelle
Es ist erwähnenswert, dass es für die Bereinigung einer "schweren" Ausnahme, die nur außerhalb eines bestimmten Codeblocks abgefangen wird, möglich ist, eine Ausnahme auszulösen, die darin abgefangen und behandelt wird. Mithilfe von Ausnahmefiltern (verfügbar in vb.net, jedoch nicht in C #) kann dieser Zustand erkannt werden. Es gibt nicht viel, was Code tun kann, um damit "umzugehen". Wenn man jedoch ein Protokollierungsframework verwendet, lohnt es sich mit ziemlicher Sicherheit, es zu protokollieren. Der C ++ - Ansatz, dass Ausnahmen, die innerhalb der Bereinigung auftreten, einen Systemausfall auslösen, ist hässlich, aber das Verschwinden von Ausnahmen ist meiner Meinung nach abscheulich.
Supercat
1

Vor einigen Monaten stand ich auch so etwas gegenüber,

    private  void RaiseException(String errorMessage)
    {
        throw new Exception(errorMessage);
    }

    private  void DoTaskForFinally()
    {
        RaiseException("Error for finally");
    }

    private  void DoTaskForCatch()
    {
        RaiseException("Error for catch");
    }

    private  void DoTaskForTry()
    {
        RaiseException("Error for try");
    }


        try
        {
            /*lacks the exception*/
            DoTaskForTry();
        }
        catch (Exception exception)
        {
            /*lacks the exception*/
            DoTaskForCatch();
        }
        finally
        {
            /*the result exception*/
            DoTaskForFinally();
        }

Um dieses Problem zu lösen, habe ich eine Utility-Klasse wie erstellt

class ProcessHandler : Exception
{
    private enum ProcessType
    {
        Try,
        Catch,
        Finally,
    }

    private Boolean _hasException;
    private Boolean _hasTryException;
    private Boolean _hasCatchException;
    private Boolean _hasFinnallyException;

    public Boolean HasException { get { return _hasException; } }
    public Boolean HasTryException { get { return _hasTryException; } }
    public Boolean HasCatchException { get { return _hasCatchException; } }
    public Boolean HasFinnallyException { get { return _hasFinnallyException; } }
    public Dictionary<String, Exception> Exceptions { get; private set; } 

    public readonly Action TryAction;
    public readonly Action CatchAction;
    public readonly Action FinallyAction;

    public ProcessHandler(Action tryAction = null, Action catchAction = null, Action finallyAction = null)
    {

        TryAction = tryAction;
        CatchAction = catchAction;
        FinallyAction = finallyAction;

        _hasException = false;
        _hasTryException = false;
        _hasCatchException = false;
        _hasFinnallyException = false;
        Exceptions = new Dictionary<string, Exception>();
    }


    private void Invoke(Action action, ref Boolean isError, ProcessType processType)
    {
        try
        {
            action.Invoke();
        }
        catch (Exception exception)
        {
            _hasException = true;
            isError = true;
            Exceptions.Add(processType.ToString(), exception);
        }
    }

    private void InvokeTryAction()
    {
        if (TryAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasTryException, ProcessType.Try);
    }

    private void InvokeCatchAction()
    {
        if (CatchAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasCatchException, ProcessType.Catch);
    }

    private void InvokeFinallyAction()
    {
        if (FinallyAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasFinnallyException, ProcessType.Finally);
    }

    public void InvokeActions()
    {
        InvokeTryAction();
        if (HasTryException)
        {
            InvokeCatchAction();
        }
        InvokeFinallyAction();

        if (HasException)
        {
            throw this;
        }
    }
}

Und so verwendet

try
{
    ProcessHandler handler = new ProcessHandler(DoTaskForTry, DoTaskForCatch, DoTaskForFinally);
    handler.InvokeActions();
}
catch (Exception exception)
{
    var processError = exception as ProcessHandler;
    /*this exception contains all exceptions*/
    throw new Exception("Error to Process Actions", exception);
}

Wenn Sie jedoch Parameter und Rückgabetypen verwenden möchten, ist dies eine andere Geschichte

Dipon Roy
quelle
1
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

Die Art und Weise, wie die von CodeA und CodeB ausgelösten Ausnahmen behandelt werden, ist dieselbe.

Eine in einem finallyBlock ausgelöste Ausnahme hat nichts Besonderes. Behandeln Sie sie als den durch Code B ausgelösten Ausnahmefall.

Cheng Chen
quelle
Könnten Sie näher darauf eingehen? Was meinst du mit den gleichen Ausnahmen?
Dirk Vollmar
1

Die Ausnahme breitet sich aus und sollte auf einer höheren Ebene behandelt werden. Wenn die Ausnahme nicht auf der höheren Ebene behandelt wird, stürzt die Anwendung ab. Die Blockausführung "finally" stoppt an dem Punkt, an dem die Ausnahme ausgelöst wird.

Unabhängig davon, ob es eine Ausnahme gibt oder nicht, wird die Ausführung des Blocks "finally" garantiert.

  1. Wenn der "finally" -Block ausgeführt wird, nachdem im try-Block eine Ausnahme aufgetreten ist,

  2. und wenn diese Ausnahme nicht behandelt wird

  3. und wenn der finally-Block eine Ausnahme auslöst

Dann geht die ursprüngliche Ausnahme, die im try-Block aufgetreten ist, verloren.

public class Exception
{
    public static void Main()
    {
        try
        {
            SomeMethod();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    public static void SomeMethod()
    {
        try
        {
            // This exception will be lost
            throw new Exception("Exception in try block");
        }
        finally
        {
            throw new Exception("Exception in finally block");
        }
    }
} 

Toller Artikel für Details

Raj Baral
quelle
-1

Es wird eine Ausnahme ausgelöst;) Sie können diese Ausnahme in einer anderen catch-Klausel abfangen.

JHollanti
quelle