¿Se activará el código en una instrucción Finalmente si devuelvo un valor en un bloque Try?

237

Estoy revisando un código para un amigo y digo que estaba usando una declaración de devolución dentro de un bloque try-finally. ¿El código en la sección Finalmente todavía se activa aunque el resto del bloque try no?

Ejemplo:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}
JamesEggers
fuente
Ser manejado aquí significa: atrapado. Incluso un bloque de captura vacío en su controlador global es suficiente. Además, hay excepciones que no se pueden manejar: StackOverflowException, ExecutionEngineExceptionson algunos de ellos. Y como no se pueden manejar, finallyno se ejecutarán.
Abel
8
@Abel: Parece que estás hablando de una situación diferente. Esta pregunta es sobre regresar en un trybloque. Nada sobre abortos abruptos del programa.
Jon Skeet
66
@Abel: No estoy seguro de lo que quieres decir con "regresar después de una excepción", pero eso no parece ser lo que se pregunta aquí. Mire el código: la primera declaración del trybloque es una returndeclaración. (La segunda declaración de ese bloque es inalcanzable y generará una advertencia.)
Jon Skeet
44
@Abel: De hecho, y si la pregunta hubiera sido "Se codificará en una declaración final siempre se ejecutará en cada situación", eso habría sido relevante. Pero eso no fue lo que se le preguntó.
Jon Skeet

Respuestas:

265

Respuesta simple: sí.

Andrew Rollings
fuente
99
@Edi: Um, no veo qué hilos de fondo tienen que ver con eso. Básicamente, a menos que se cancele todo el proceso, el finallybloque se ejecutará.
Jon Skeet
3
La respuesta larga es que no necesariamente se ejecutará un bloqueo final si ha sucedido algo catastrófico, como un desbordamiento de pila, una excepción de falta de memoria, un bloqueo grave de algún tipo, o si alguien desconecta su máquina en el momento justo . Pero para todos los efectos, a menos que esté haciendo algo muy muy malo, el bloque finalmente siempre se disparará.
Andrew Rollings
Si el código antes del intento falla debido a alguna razón, he notado que finalmente todavía se ejecuta. En ese caso, puede agregar una condición para finalmente que satisfaga los criterios para ejecutarse finalmente solo cuando el código bajo se ejecute con éxito
kjosh
200

Normalmente si. La última sección está garantizada para ejecutar lo que suceda, incluidas las excepciones o la declaración de devolución. Una excepción a esta regla es una excepción asincrónica que ocurre en el hilo ( OutOfMemoryException, StackOverflowException).

Para obtener más información sobre las excepciones asíncronas y el código confiable en esas situaciones, lea sobre las regiones de ejecución restringidas .

Mehrdad Afshari
fuente
154

Aquí hay una pequeña prueba:

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

El resultado es:

before
finally
return
after
Jon B
fuente
10
@CodeBlend: tiene que ver con cuándo WriteLinese realiza realmente el resultado de la llamada al método. En este caso, la returnllamada establece el resultado de los métodos, finalmente escribe en la consola, antes de que salga el método. Luego, WriteLineen el método Principal escupe el texto de la llamada de retorno.
NotMe
38

Citando desde MSDN

Finalmente se utiliza para garantizar que un bloque de código de instrucción se ejecute independientemente de cómo se salga del bloque try anterior .

Codificador perpetuo
fuente
3
Bueno, además de los casos excepcionales de Mehrdad, finalmente tampoco se llamará si está depurando en un bloque de prueba y luego detiene la depuración :). Parece que nada en la vida está garantizado.
StuartLC
1
No del todo, citando de MSDN : Sin embargo, si la excepción no se maneja, la ejecución del último bloque depende de cómo se desencadene la operación de desenrollado de excepción. Eso, a su vez, depende de cómo esté configurada su computadora. .
Abel
1
@Abel hace un punto crucial aquí. Todos pueden ver cómo finalmente no se ejecuta un bloque si, por ejemplo, el programa se cancela a través del administrador de tareas. Pero esta respuesta es, tal como está, completamente equivocada: finallyde hecho, no garantiza nada incluso para programas perfectamente ordinarios (lo que resultó en esta excepción).
Peter - Restablece a Monica el
19

En general sí, finalmente se ejecutará.

Para los siguientes tres escenarios, el SIEMPRE se ejecutará SIEMPRE :

  1. No se producen excepciones.
  2. Excepciones sincrónicas (excepciones que ocurren en el flujo normal del programa).
    Esto incluye excepciones compatibles con CLS que se derivan de System.Exception y excepciones no compatibles con CLS, que no se derivan de System.Exception. RuntimeWrappedException ajusta automáticamente las excepciones que no cumplen con CLS. C # no puede lanzar excepciones de reclamos que no sean de CLS, pero sí pueden lenguajes como C ++. C # podría estar llamando a un código escrito en un lenguaje que puede generar excepciones no compatibles con CLS.
  3. ThreadAbortException asíncrona
    A partir de .NET 2.0, una ThreadAbortException ya no impedirá que finalmente se ejecute. ThreadAbortException ahora se iza antes o después de finalmente. El finalmente siempre se ejecutará y no será interrumpido por un aborto de hilo, siempre que el intento se haya introducido antes de que ocurriera el aborto de hilo.

El siguiente escenario, el finalmente no se ejecutará:

StackOverflowException asíncrono.
A partir de .NET 2.0, un desbordamiento de la pila hará que el proceso finalice. Finalmente, no se ejecutará, a menos que se aplique una restricción adicional para que finalmente sea un CER (Región de ejecución restringida). Los CER no deben usarse en el código de usuario general. Solo deben usarse donde sea crítico que el código de limpieza siempre se ejecute, ya que de todos modos el proceso se está cerrando en el desbordamiento de la pila y, por lo tanto, todos los objetos administrados se limpiarán de manera predeterminada. Por lo tanto, el único lugar donde un CER debería ser relevante es para los recursos que se asignan fuera del proceso, por ejemplo, los identificadores no administrados.

Por lo general, el código no administrado lo envuelve alguna clase administrada antes de ser consumido por el código de usuario. La clase de contenedor administrado generalmente utilizará un SafeHandle para ajustar el identificador no administrado. SafeHandle implementa un finalizador crítico y un método de lanzamiento que se ejecuta en un CER para garantizar la ejecución del código de limpieza. Por este motivo, no debería ver CERs llenos de código de usuario completo.

Entonces, el hecho de que finalmente no se ejecute en StackOverflowException no debería tener ningún efecto en el código del usuario, ya que el proceso finalizará de todos modos. Si tiene algún caso límite en el que necesita limpiar algún recurso no administrado, fuera de SafeHandle o CriticalFinalizerObject, utilice un CER de la siguiente manera; pero tenga en cuenta que esta es una mala práctica: el concepto no administrado debe abstraerse a una (s) clase (s) administrada (s) y SafeHandle (s) apropiados por diseño.

p.ej,

// 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
fuente
Tenga en cuenta que incluso un CER no se ejecutará en el caso de un SOE. En el momento en que escribió esto, la documentación de MSDN en CER era incorrecta / incompleta. Un SOE activará un FailFastinterno. La única forma en que pude atraparlos fue personalizando el alojamiento de tiempo de ejecución CLR . Tenga en cuenta que su punto sigue siendo válido para algunas otras excepciones asincrónicas.
Abel
10

Hay una excepción muy importante a esto que no he visto mencionado en ninguna otra respuesta y que (después de programar en C # durante 18 años) no puedo creer que no lo supiera.

Si lanza o activa una excepción de cualquier tipo dentro de sucatch bloque (no solo extraños StackOverflowExceptionsy cosas de ese tipo), y no tiene todo el try/catch/finallybloque dentro de otro try/catchbloque, su finallybloque no se ejecutará. Esto se demuestra fácilmente, y si no lo hubiera visto yo mismo, dada la frecuencia con la que he leído que solo son casos de esquina muy extraños y pequeños que pueden hacer que un finallybloque no se ejecute, no lo habría creído.

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

Estoy seguro de que hay una razón para esto, pero es extraño que no se conozca más ampliamente. (Se nota aquí, por ejemplo, pero no en ninguna parte de esta pregunta en particular).

Ken Smith
fuente
Bueno, el finallybloque simplemente no es una trampa para el catchbloque.
Peter - Restablece a Monica el
7

Me doy cuenta de que llego tarde a la fiesta, pero en el escenario (que difiere del ejemplo del OP) donde de hecho se lanza una excepción a los estados de MSDN ( https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx ): "Si no se detecta la excepción, la ejecución del bloque finalmente depende de si el sistema operativo elige activar una operación de desenrollado de excepción".

El último bloque solo se garantiza para ejecutarse si alguna otra función (como Principal) más arriba en la pila de llamadas detecta la excepción. Este detalle generalmente no es un problema porque todos los programas C # de entornos de tiempo de ejecución (CLR y OS) se ejecutan en la mayoría de los recursos libres que posee un proceso cuando sale (identificadores de archivos, etc.). Sin embargo, en algunos casos puede ser crucial: una operación de base de datos a mitad de camino en la que desea comprometerse relajarse; o alguna conexión remota que puede no ser cerrada automáticamente por el sistema operativo y luego bloquea un servidor.

Peter - Restablece a Monica
fuente
3

Si. De hecho, ese es el punto principal de una declaración final. A menos que ocurra algo catastrófico (sin memoria, computadora desconectada, etc.), la declaración final siempre debe ejecutarse.

Jeffrey L Whitledge
fuente
1
Ruego no estar de acuerdo. Cf. mi respuesta. Una excepción perfectamente normal en el bloque try es suficiente para omitir el finallybloque si esa excepción nunca se detecta. Eso me tomó por sorpresa ;-).
Peter - Restablece a Monica el
@ PeterA.Schneider - Eso es interesante. Por supuesto, las implicaciones de eso realmente no cambian mucho. Si nada en la pila de llamadas detectará la excepción, entonces eso debería significar (si lo entiendo correctamente) que el proceso está a punto de finalizar. Por lo tanto, es similar a la situación de tirar del enchufe. Supongo que la conclusión de esto es que siempre debe tener una captura de nivel superior o excepción no controlada.
Jeffrey L Whitledge
Exactamente, eso es lo que entiendo también. También me pareció sorprendente. Verdadero: los recursos más comunes se liberarán automáticamente, en particular la memoria y los archivos abiertos. Pero los recursos que el sistema operativo no conoce (conexiones de bases de datos o servidores abiertos como ejemplo) pueden permanecer abiertos en el lado remoto porque nunca se han cerrado correctamente; los enchufes siguen persistiendo, etc. Supongo que es prudente tener un controlador de excepciones de nivel superior, sí.
Peter - Restablece a Monica el
2

finalmente no se ejecutará en caso de que salga de la aplicación usando System.exit (0); como en

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

el resultado sería solo: intente

Hakuna Matata
fuente
44
Está respondiendo en el idioma equivocado, supongo, la pregunta era sobre c#, pero parece ser Java. Y además de eso, en la mayoría de los casos System.exit()es un indicio de un mal diseño :-)
z00l
0

En el 99% de los escenarios se garantizará que el código dentro del finallybloque se ejecutará, sin embargo, piense en este escenario: tiene un hilo que tiene un bloque try-> finally(no catch) y obtiene una excepción no controlada dentro de ese hilo. En este caso, el hilo saldrá y sufinally bloque no se ejecutará (la aplicación puede continuar ejecutándose en este caso)

Este escenario es bastante raro, pero es solo para mostrar que la respuesta NO SIEMPRE es "Sí", es la mayoría de las veces "Sí" y, a veces, en raras ocasiones, "No".

Jonathan Perry
fuente
0

El objetivo principal de finalmente bloquear es ejecutar lo que esté escrito dentro de él. No debe depender de lo que suceda en try o catch. Sin embargo, con System.Environment.Exit (1) la aplicación saldrá sin pasar a la siguiente línea de código.

Ashish Sahu
fuente