Cosa succede se un blocco finally genera un'eccezione?


266

Se un blocco finally genera un'eccezione, cosa succede esattamente ?

In particolare, cosa succede se l'eccezione viene generata a metà del blocco infine. Le altre istruzioni (dopo) in questo blocco vengono invocate?

Sono consapevole che le eccezioni si propagheranno verso l'alto.


8
Perché non provarlo? Ma su questo genere di cose, quello che mi piace di più è il ritorno prima dell'ultimo e poi il ritorno di qualcos'altro dall'ultimo blocco. :)
ANeves,

8
Tutte le istruzioni in un blocco finally devono essere eseguite. Non può avere un ritorno. msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Tim Scarborough,

Risposte:


419

Se un blocco finalmente genera un'eccezione, cosa succede esattamente ?

Quell'eccezione si propaga fino in fondo e sarà (può) essere gestita a un livello superiore.

Il blocco finally non verrà completato oltre il punto in cui viene generata l'eccezione.

Se il blocco finally era in esecuzione durante la gestione di un'eccezione precedente, la prima eccezione viene persa.

Specifica lingua C # 4 § 8.9.5: Se il blocco finally genera un'altra eccezione, l'elaborazione dell'eccezione corrente viene terminata.


9
A meno che non sia un ThreadAbortException, allora l'intero blocco infine sarà completato per primo, poiché è una sezione critica.
Dmytro Shevchenko l'

1
@Shedal: hai ragione, ma questo vale solo per "determinate eccezioni asincrone", ovvero ThreadAbortException. Per il normale codice a 1 thread la mia risposta vale.
Henk Holterman,

"La prima eccezione viene persa" - in realtà è molto deludente, per caso trovo oggetti IDisposable che generano eccezioni in Dispose (), che si traducono nella perdita dell'eccezione nella clausola "using".
Alex Burtsev,

"Trovo oggetti IDisposable che generano eccezioni in Dispose ()" - è a dir poco strano. Leggi su MSDN: EVITARE di lanciare un'eccezione dall'interno di Dispose (bool) tranne che sotto ...
Henk Holterman

1
@HenkHolterman: gli errori del disco pieno non sono molto comuni su un disco rigido primario direttamente collegato, ma i programmi a volte scrivono file su dischi rimovibili o collegati in rete; i problemi possono essere molto più comuni con quelli. Se qualcuno estrae una chiavetta USB prima che un file sia stato completamente scritto, sarebbe meglio dirgli immediatamente che attendere fino a quando non arrivano dove stanno andando e scoprire che il file è corrotto. Cedere all'errore precedente quando c'è uno può essere un comportamento ragionevole, ma quando non c'è errore precedente sarebbe meglio segnalare il problema piuttosto che lasciarlo non segnalato.
supercat,

101

Per domande come queste di solito apro un progetto di applicazione console vuota in Visual Studio e scrivo un piccolo programma di esempio:

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

Quando si esegue il programma si vedrà l'ordine esatto in cui catche finallyblocchi vengono eseguiti. Nota che il codice nel blocco finally dopo che viene generata l'eccezione non verrà eseguito (in effetti, in questo programma di esempio Visual Studio ti avvertirà persino che ha rilevato un codice non raggiungibile):

Eccezione di gestione del blocco catch interno generata dal blocco try.
Interno finalmente blocco
Eccezione di gestione del blocco catch esterno generata dal blocco infine.
L'esterno infine si blocca

Nota aggiuntiva

Come ha sottolineato Michael Damatov, un'eccezione dal tryblocco verrà "mangiata" se non la gestisci in un catchblocco (interno) . In effetti, nell'esempio sopra l'eccezione nuovamente generata non appare nel blocco catch esterno. Per rendere ancora più chiaro il seguente esempio leggermente modificato:

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

Come puoi vedere dall'output, l'eccezione interna viene "persa" (cioè ignorata):

Interno finalmente blocco
Eccezione di gestione del blocco catch esterno generata dal blocco infine.
L'esterno infine si blocca

2
Dal momento che GETTI l'eccezione nel tuo pescato interiore, in questo esempio non si raggiungerà mai "Blocco finalmente interiore"
Theofanis Pantelides,

4
@Theofanis Pantelides: No, un finallyblocco verrà (quasi) sempre eseguito, questo vale anche in questo caso per il blocco inner finally (basta provare il programma di esempio da soli (Un blocco finally non verrà eseguito nel caso di un non recuperabile eccezione, ad esempio un EngineExecutionException, ma in tal caso il programma terminerà immediatamente comunque).
Dirk Vollmar,

1
Tuttavia, non vedo quale sia il ruolo del lancio nella prima cattura del tuo primo pezzo di codice. Ho provato con e senza di essa con un'applicazione console, nessuna differenza trovata.
John Pan

@johnpan: il punto era mostrare che il blocco finally viene sempre eseguito, anche se entrambi cercano di catturare un'eccezione. Non c'è davvero alcuna differenza nell'output della console.
Dirk Vollmar,

10

Se è in sospeso un'eccezione (quando il tryblocco ha un finallyma no catch), la nuova eccezione sostituisce quella.

Se non vi sono eccezioni in sospeso, funziona esattamente come generare un'eccezione all'esterno del finallyblocco.


Un'eccezione potrebbe anche essere sospeso se v'è un corrispondente catchblocco che (ri) genera un'eccezione.
stakx - non contribuisce più al


3

Frammento rapido (e piuttosto ovvio) per salvare "eccezione originale" (generata in tryblocco) e sacrificare "infine eccezione" (generata in finallyblocco), nel caso in cui l'originale sia più importante per te:

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

Quando viene eseguito il codice sopra, "Eccezione originale" si propaga nello stack di chiamate e "Infine Eccezione" viene persa.


2

Ho dovuto farlo per rilevare un errore nel tentativo di chiudere un flusso che non è mai stato aperto a causa di un'eccezione.

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

se il webRequest è stato creato ma si è verificato un errore di connessione durante il

using (var sw = webRequest.GetRequestStream())

quindi finalmente avrebbe rilevato un'eccezione nel tentativo di chiudere le connessioni che pensava fossero aperte perché il WebRequest era stato creato.

Se alla fine non fosse presente un tentativo-catch all'interno, questo codice causerebbe un'eccezione non gestita durante la pulizia del webRequest

if (webRequest.GetRequestStream() != null) 

da lì il codice uscirebbe senza gestire correttamente l'errore che si è verificato e quindi causare problemi per il metodo di chiamata.

Spero che questo aiuti come esempio


1

Se viene generata un'eccezione mentre è attiva un'altra eccezione, la prima eccezione verrà sostituita dalla seconda (successiva).

Ecco un po 'di codice che illustra cosa succede:

    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);
        }
    }
  • Esegui il codice e vedrai la "seconda eccezione"
  • Riattiva le dichiarazioni try and catch e vedrai la "prima eccezione"
  • Anche decommentare il tiro; e vedrai di nuovo la "seconda eccezione".

Vale la pena notare che è possibile ripulire un'eccezione "grave" che verrebbe catturata solo al di fuori di un determinato blocco di codice per generare un'eccezione che viene catturata e gestita al suo interno. Utilizzando i filtri delle eccezioni (disponibili in vb.net, sebbene non in C #) è possibile rilevare questa condizione. Non c'è molto che il codice possa fare per "gestirlo", anche se se si utilizza qualsiasi tipo di framework di registrazione è quasi sicuramente utile registrarlo. L'approccio C ++ di avere eccezioni che si verificano all'interno del cleanup innesca un crollo del sistema è brutto, ma far scomparire le eccezioni è IMHO orribile.
supercat

1

Alcuni mesi fa ho anche affrontato qualcosa del genere,

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

Per risolvere questo problema ho creato una classe di utilità come

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

E usato così

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

ma se vuoi usare i parametri e restituire i tipi questa è un'altra storia


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

Il modo in cui vengono gestite le eccezioni generate da CodeA e CodeB è lo stesso.

Un'eccezione generata in un finallyblocco non ha nulla di speciale, trattala come un'eccezione generata dal codice B.


Potresti elaborare? Cosa intendi con le eccezioni sono le stesse?
Dirk Vollmar,

1

L'eccezione si propaga e dovrebbe essere gestita a un livello superiore. Se l'eccezione non viene gestita al livello superiore, l'applicazione si arresta in modo anomalo. L'esecuzione del blocco "finally" si interrompe nel punto in cui viene generata l'eccezione.

Indipendentemente dal fatto che esista o meno un'eccezione, il blocco "finalmente" è garantito.

  1. Se il blocco "finally" viene eseguito dopo che si è verificata un'eccezione nel blocco try,

  2. e se tale eccezione non viene gestita

  3. e se il blocco finally genera un'eccezione

Quindi l'eccezione originale che si è verificata nel blocco try viene persa.

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

Ottimo articolo per i dettagli


-1

Genera un'eccezione;) Puoi catturare quell'eccezione in qualche altra clausola catch.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.