Il codice in un'istruzione Final viene attivato se restituisco un valore in un blocco Try?


237

Sto rivedendo un codice per un amico e dico che stava usando un'istruzione return all'interno di un blocco try-finally. Il codice nella sezione Finalmente continua a funzionare anche se il resto del blocco try no?

Esempio:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}


Essere gestiti qui significa: catturati. È sufficiente anche un blocco di cattura vuoto nel gestore globale. Inoltre, ci sono eccezioni che non possono essere gestiti: StackOverflowException, ExecutionEngineExceptionsono alcuni di quelli. E poiché non possono essere gestiti, il finallynon funzionerà.
Abel,

8
@Abel: sembra che tu stia parlando di una situazione diversa. Questa domanda riguarda il ritorno in un tryblocco. Niente si interrompe improvvisamente.
Jon Skeet,

6
@Abel: Non sono sicuro di cosa intendi per "ritorno dopo un'eccezione", ma questo non sembra essere ciò che viene chiesto qui. Guarda il codice: la prima istruzione del tryblocco è returnun'istruzione. (La seconda affermazione di quel blocco è irraggiungibile e genererà un avviso.)
Jon Skeet,

4
@Abel: In effetti, e se la domanda fosse stata "Il codice di Will in un'istruzione finally si eseguirà sempre in ogni situazione" sarebbe rilevante. Ma non è quello che è stato chiesto.
Jon Skeet,

Risposte:


265

Risposta semplice: Sì.


9
@Edi: Uhm, non vedo cosa hanno a che fare i thread in background. Fondamentalmente, a meno che l'intero processo non si interrompa, il finallyblocco verrà eseguito.
Jon Skeet,

3
La lunga risposta è che non avrai necessariamente un blocco finalmente da eseguire se si è verificato qualcosa di catastrofico, come Stack Overflow, eccezione di Memoria esaurita, un arresto anomalo di qualche tipo o se qualcuno scollega la macchina al momento giusto . Ma a tutti gli effetti, a meno che tu non stia facendo qualcosa di molto sbagliato, il blocco finalmente sparerà sempre.
Andrew Rollings,

Se il codice prima di tentare fallisce per qualche motivo, ho notato che finalmente viene ancora eseguito. In tal caso è possibile aggiungere una condizione per in che finalmente soddisfa i criteri per eseguire finalmente solo quando il codice sotto viene eseguito correttamente
kjosh

200

Normalmente si. La sezione finally è garantita per eseguire qualunque cosa accada, comprese le eccezioni o la dichiarazione di ritorno. Un'eccezione a questa regola è un'eccezione asincrona che si verifica sul thread ( OutOfMemoryException, StackOverflowException).

Per ulteriori informazioni sulle eccezioni asincrone e sul codice affidabile in tali situazioni, leggi le aree di esecuzione vincolate .


154

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

Il risultato è:

before
finally
return
after

10
@CodeBlend: ha a che fare quando WriteLineviene effettivamente eseguito il risultato della chiamata al metodo. In questo caso la returnchiamata imposta il risultato dei metodi, infine scrive sulla console - prima che il metodo esca. Quindi il WriteLinemetodo in principale sputa il testo dalla chiamata di ritorno.
NotMe

38

Citando da MSDN

infine viene utilizzato per garantire l'esecuzione di un blocco di istruzioni di codice indipendentemente da come viene terminato il blocco di prova precedente .


3
Bene, oltre ai casi eccezionali di Mehrdad, alla fine non verrà chiamato anche se si esegue il debug in un blocco try e quindi si interrompe il debug :). Nulla nella vita è garantito, a quanto pare.
StuartLC,

1
Non proprio, citando da MSDN : Tuttavia, se l'eccezione non viene gestita, l'esecuzione del blocco finally dipende da come viene attivata l'operazione di svolgimento dell'eccezione. Ciò, a sua volta, dipende dalla configurazione del computer. .
Abel,

1
@Abel fa un punto cruciale qui. Tutti possono vedere come un blocco finally non viene eseguito se, ad esempio, il programma viene interrotto tramite Task Manager. Ma questa risposta è, per così dire, chiaramente sbagliata: finallyin realtà non garantisce nulla nemmeno per i programmi perfettamente ordinari (che è successo a lanciare questa eccezione).
Peter - Ripristina Monica il

19

Generalmente sì, finalmente verrà eseguito.

Per i seguenti tre scenari, finalmente verrà eseguito SEMPRE :

  1. Non si verificano eccezioni
  2. Eccezioni sincrone (eccezioni che si verificano nel normale flusso del programma).
    Ciò include le eccezioni conformi a CLS che derivano da System.Exception e le eccezioni non conformi a CLS, che non derivano da System.Exception. Le eccezioni non conformi a CLS vengono automaticamente racchiuse da RuntimeWrappedException. C # non può generare eccezioni di reclamo non CLS, ma possono farlo linguaggi come C ++. C # potrebbe chiamare il codice scritto in una lingua che può generare eccezioni non conformi a CLS.
  3. ThreadAbortException asincrona
    A partire da .NET 2.0, ThreadAbortException non impedisce più l'esecuzione di. ThreadAbortException è ora sollevato prima o dopo il fine. L'elaborazione finale verrà sempre eseguita e non verrà interrotta da un'interruzione del thread, purché il tentativo sia stato effettivamente inserito prima dell'interruzione del thread.

Il seguente scenario, finalmente non verrà eseguito:

StackOverflowException asincrono.
A partire da .NET 2.0 un overflow dello stack farà terminare il processo. L'ultimo non verrà eseguito, a meno che non venga applicato un ulteriore vincolo per rendere finalmente un CER (Constrained Execution Region). I CER non devono essere utilizzati nel codice utente generale. Dovrebbero essere usati solo laddove è fondamentale che il codice di pulizia venga sempre eseguito, dopo che tutto il processo viene comunque interrotto in caso di overflow dello stack e tutti gli oggetti gestiti verranno quindi ripuliti per impostazione predefinita. Pertanto, l'unico posto in cui una CER dovrebbe essere pertinente è per le risorse che sono allocate al di fuori del processo, ad esempio, handle non gestiti.

In genere, il codice non gestito viene racchiuso da una classe gestita prima di essere utilizzato dal codice utente. La classe wrapper gestita in genere utilizzerà un SafeHandle per avvolgere l'handle non gestito. SafeHandle implementa un finalizzatore critico e un metodo di rilascio che viene eseguito in un CER per garantire l'esecuzione del codice di pulizia. Per questo motivo, non dovresti vedere i CER disseminati nel codice utente.

Quindi il fatto che finalmente non venga eseguito su StackOverflowException non dovrebbe avere alcun effetto sul codice utente, poiché il processo verrà comunque terminato. Se si dispone di un caso limite in cui è necessario ripulire alcune risorse non gestite, al di fuori di SafeHandle o CriticalFinalizerObject, utilizzare un CER come segue; ma tieni presente che si tratta di una cattiva pratica: il concetto non gestito dovrebbe essere estratto in una (e) classe (i) gestita e SafeHandle (s) appropriato in base alla progettazione.

per esempio,

// 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.
}

Si noti che anche un CER non verrà eseguito nel caso di una SOE. Al momento in cui lo hai scritto, la documentazione MSDN su CER era errata / incompleta. Un SOE attiverà un FailFastinternamente. L'unico modo in cui sono riuscito a catturarli è personalizzare l'hosting di runtime CLR . Nota che il tuo punto è ancora valido per alcune altre eccezioni asincrone.
Abel,

10

C'è un'eccezione molto importante a ciò che non ho visto menzionato in altre risposte e che (dopo aver programmato in C # per 18 anni) non posso credere di non saperlo.

Se lanci o attivi un'eccezione di qualsiasi tipo all'interno del tuo catchblocco (non solo strano StackOverflowExceptionse cose del genere) e non hai l'intero try/catch/finallyblocco all'interno di un altro try/catchblocco, il tuo finallyblocco non verrà eseguito. Ciò è facilmente dimostrato - e se non lo avessi visto da solo, data la frequenza con cui ho letto che sono solo dei casi angolari davvero strani, che possono far sì che un finallyblocco non venga eseguito, non ci avrei creduto.

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

Sono sicuro che c'è una ragione per questo, ma è strano che non sia più conosciuto. (È notato qui per esempio, ma non da nessuna parte in questa particolare domanda.)


Bene, il finallyblocco semplicemente non è una cattura per il catchblocco.
Peter - Ripristina Monica il

7

Mi rendo conto di essere in ritardo alla festa, ma nello scenario (diverso dall'esempio del PO) in cui viene effettivamente generata un'eccezione negli stati MSDN ( https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx ): "Se l'eccezione non viene rilevata, l'esecuzione del blocco finally dipende dal fatto che il sistema operativo scelga di attivare un'operazione di svolgimento dell'eccezione."

Il blocco finally è garantito solo se qualche altra funzione (come Main) più in alto nello stack di chiamate rileva l'eccezione. Questo dettaglio di solito non è un problema perché tutti i programmi C # degli ambienti di runtime (CLR e OS) vengono eseguiti sulla maggior parte delle risorse libere che un processo possiede quando esce (handle di file, ecc.). In alcuni casi può essere cruciale: un'operazione di database a metà in corso che si desidera impegnare resp. rilassarsi; o qualche connessione remota che potrebbe non essere chiusa automaticamente dal sistema operativo e quindi bloccare un server.


3

Sì. Questo è in effetti quel punto principale di una dichiarazione finalmente. A meno che non si verifichi qualcosa di catastrofico (memoria insufficiente, computer scollegato, ecc.), L'istruzione finally deve sempre essere eseguita.


1
Mi permetto di non essere d'accordo. Cf. la mia risposta. Un'eccezione perfettamente normale nel blocco try è sufficiente per bypassare il finallyblocco se tale eccezione non viene mai rilevata. Che ha attirato me di sorpresa ;-).
Peter - Ripristina Monica il

@ PeterA.Schneider - È interessante. Naturalmente, le implicazioni di ciò non cambiano molto. Se nulla nello stack di chiamate catturerà l'eccezione, ciò dovrebbe significare (se ho capito bene) che il processo sta per essere terminato. Quindi è simile alla situazione di staccare la spina. Immagino che da asporto sia che dovresti sempre avere una cattura di eccezione di livello superiore o non gestita.
Jeffrey L Whitledge,

Esatto, è quello che capisco anche io. L'ho trovato anche sorprendente. Vero: le risorse più comuni verranno rilasciate automaticamente, in particolare memoria e file aperti. Ma le risorse di cui il sistema operativo non è a conoscenza - ad esempio connessioni aperte al server o al database - potrebbero rimanere aperte sul lato remoto perché non sono mai state correttamente chiuse; le prese continuano a persistere, ecc. Immagino sia prudente avere un gestore di eccezioni di livello superiore, sì.
Peter - Ripristina Monica il


2

infine non verrà eseguito nel caso in cui si esce dall'applicazione utilizzando System.exit (0); come in

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

il risultato sarebbe giusto: provare


4
Stai rispondendo nella lingua sbagliata, suppongo, la domanda riguardava c#, ma questo sembra essere Java. E oltre a ciò, nella maggior parte dei casi System.exit()è un accenno di un cattivo design :-)
z00l

0

Il 99% degli scenari sarà garantito che il codice all'interno del finallyblocco verrà eseguito, tuttavia, pensa a questo scenario: hai un thread che ha un blocco try-> finally(no catch) e ottieni un'eccezione non gestita all'interno di quel thread. In questo caso, il thread uscirà e il suofinally blocco non verrà eseguito (in questo caso l'applicazione può continuare a funzionare)

Questo scenario è piuttosto raro, ma è solo per dimostrare che la risposta non è SEMPRE "Sì", è per lo più "Sì" e talvolta, in rare condizioni, "No".


0

Lo scopo principale del blocco finalmente è quello di eseguire tutto ciò che è scritto al suo interno. Non dovrebbe dipendere da qualsiasi cosa accada in try o catch. Tuttavia, con System.Environment.Exit (1) l'applicazione verrà chiusa senza passare alla riga di codice successiva.

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.