¿Qué sucede si un bloque finalmente arroja una excepción?


266

Si un bloque finalmente arroja una excepción, ¿ qué sucede exactamente ?

Específicamente, qué sucede si la excepción se lanza a la mitad de un bloque finalmente. ¿Se invoca el resto de las declaraciones (después) en este bloque?

Soy consciente de que las excepciones se propagarán hacia arriba.


8
¿Por qué no solo probarlo? Pero en este tipo de cosas, la que más me gusta es regresar antes del finalmente y luego devolver algo más del bloque finalmente. :)
ANeves

8
Todas las declaraciones en un bloque finalmente deben ejecutarse. No puede tener un retorno. msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Tim Scarborough

Respuestas:


419

Si un bloque finalmente arroja una excepción, ¿ qué sucede exactamente ?

Esa excepción se propaga hacia arriba y hacia arriba, y se (puede) manejar en un nivel superior.

Su bloqueo final no se completará más allá del punto donde se produce la excepción.

Si el bloque finalmente se estaba ejecutando durante el manejo de una excepción anterior, entonces esa primera excepción se pierde.

C # 4 Especificación del lenguaje § 8.9.5: Si el bloque finalmente arroja otra excepción, el procesamiento de la excepción actual finaliza.


9
A menos que sea un ThreadAbortException, entonces todo el bloque finalmente se terminará primero, ya que es una sección crítica.
Dmytro Shevchenko

1
@Shedal: tiene razón, pero eso solo se aplica a "ciertas excepciones asincrónicas", es decir, ThreadAbortException. Para el código normal de 1 hilo, mi respuesta es válida.
Henk Holterman

"La primera excepción se pierde", eso es realmente muy decepcionante, de vez en cuando encuentro objetos desechables que arrojan una excepción en Dispose (), lo que resulta en una excepción que se pierde dentro de la cláusula "using".
Alex Burtsev

"Encuentro objetos desechables ID que arrojan una excepción en Dispose ()" , eso es extraño por decir lo menos. Lea en MSDN: EVITE lanzar una excepción desde dentro de Dispose (bool) excepto bajo ...
Henk Holterman

1
@HenkHolterman: los errores de disco lleno no son muy comunes en un disco duro primario conectado directamente, pero los programas a veces escriben archivos en discos extraíbles o en red; Los problemas pueden ser mucho más comunes con ellos. Si alguien extrae una memoria USB antes de que un archivo se haya escrito completamente, sería mejor decirle de inmediato que esperar hasta llegar a donde van y encontrar que el archivo está dañado. Ceder ante el error anterior cuando hay uno puede ser un comportamiento sensato, pero cuando no hay un error anterior, sería mejor informar el problema que dejarlo sin informar.
supercat

101

Para preguntas como estas, generalmente abro un proyecto de aplicación de consola vacía en Visual Studio y escribo un pequeño programa de muestra:

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

Cuando se ejecuta el programa, verá el orden exacto en el que catchy finallybloques son ejecutados. Tenga en cuenta que el código en el bloque finalmente después de que se lanza la excepción no se ejecutará (de hecho, en este programa de ejemplo, Visual Studio incluso le advertirá que ha detectado código inalcanzable):

Excepción de manejo de bloque de captura interna lanzada desde el bloque try.
Finalmente bloque interior
Excepción de manejo de bloque de captura externa lanzada desde finalmente bloque.
Exterior finalmente bloquear

Observación adicional

Como señaló Michael Damatov, tryse "comerá" una excepción del bloque si no lo maneja en un catchbloque (interno) . De hecho, en el ejemplo anterior, la excepción relanzada no aparece en el bloque de captura externo. Para aclarar aún más la siguiente muestra ligeramente modificada:

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

Como puede ver en la salida, la excepción interna se "pierde" (es decir, se ignora):

Finalmente bloque interior
Excepción de manejo de bloque de captura externa lanzada desde finalmente bloque.
Exterior finalmente bloquear

2
Debido a que LANZAS la Excepción en tu captura interna, nunca se alcanzará el 'Bloque interior finalmente' en este ejemplo
Theofanis Pantelides,

44
@Theofanis Pantelides: No, un finallybloque (casi) siempre se ejecutará, esto también es válido en este caso para el bloque finalmente interno (solo pruebe el programa de muestra usted mismo (un bloque finalmente no se ejecutará en el caso de un no recuperable) excepción, por ejemplo, una EngineExecutionException, pero en tal caso su programa finalizará inmediatamente de todos modos)
Dirk Vollmar

1
Sin embargo, no veo cuál es el papel del lanzamiento en la primera captura de su primer código. Lo intenté con y sin él con una aplicación de consola, no encontré ninguna diferencia.
JohnPan

@johnpan: El punto era mostrar que el bloque finalmente siempre se ejecuta, incluso si ambos intentan atrapar bloque y lanzar una excepción. De hecho, no hay diferencia en la salida de la consola.
Dirk Vollmar

10

Si hay una excepción pendiente (cuando el trybloque tiene un finallypero no catch), la nueva excepción reemplaza a esa.

Si no hay una excepción pendiente, funciona igual que lanzar una excepción fuera del finallybloque.


Una excepción también puede estar pendiente si hay es una coincidencia de catchbloque que (re) emite una excepción.
stakx - ya no contribuye el


3

Fragmento rápido (y bastante obvio) para guardar la "excepción original" (arrojada en trybloque) y sacrificar la "excepción final" (arrojada en finallybloque), en caso de que la original sea más importante para usted:

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

Cuando se ejecuta el código anterior, "Excepción original" se propaga por la pila de llamadas y se pierde "Finalmente excepción".


2

Tuve que hacer esto para detectar un error al intentar cerrar una transmisión que nunca se abrió debido a una excepción.

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

si se creó webRequest pero se produjo un error de conexión durante el

using (var sw = webRequest.GetRequestStream())

entonces el finalmente detectaría una excepción al tratar de cerrar conexiones que creía que estaban abiertas porque se había creado webRequest.

Si finalmente no tuviera un try-catch dentro, este código causaría una excepción no controlada mientras limpiaba el webRequest

if (webRequest.GetRequestStream() != null) 

a partir de ahí, el código saldría sin manejar adecuadamente el error que sucedió y, por lo tanto, causar problemas para el método de llamada.

Espero que esto ayude como ejemplo


1

Lanzar una excepción mientras otra excepción está activa dará como resultado que la primera excepción sea reemplazada por la segunda (más tarde) excepción.

Aquí hay un código que ilustra lo que sucede:

    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);
        }
    }
  • Ejecute el código y verá "segunda excepción"
  • Descomente las declaraciones try y catch y verá "primera excepción"
  • También descomenta el lanzamiento; declaración y verá "segunda excepción" nuevamente.

Vale la pena señalar que es posible que la limpieza de una excepción "grave" que solo se detectaría fuera de un bloque de código en particular arroje una excepción que se detecta y maneja dentro de él. Usando filtros de excepción (disponibles en vb.net, aunque no C #) es posible detectar esta condición. No hay mucho que el código pueda hacer para "manejarlo", aunque si uno está usando algún tipo de marco de registro, es casi seguro que valga la pena hacerlo. El enfoque de C ++ de tener excepciones que ocurren dentro de la limpieza desencadenan un colapso del sistema es feo, pero tener excepciones desaparecer es horrible en mi humilde opinión.
supercat

1

Hace unos meses también me enfrenté a algo como esto,

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

Para resolver tal problema, hice una clase de utilidad como

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

Y usado así

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

pero si quieres usar parámetros y tipos de retorno, esa es otra historia


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

La forma en que se manejan las excepciones lanzadas por CodeA y CodeB es la misma.

Una excepción lanzada en un finallybloque no tiene nada especial, trátela como la excepción lanzada por el código B.


¿Podrías dar más detalles? ¿Qué quieres decir con las excepciones son las mismas?
Dirk Vollmar

1

La excepción se propaga y debe manejarse a un nivel superior. Si la excepción no se maneja en el nivel superior, la aplicación se bloquea. La ejecución del bloque "finalmente" se detiene en el punto donde se produce la excepción.

Independientemente de si hay una excepción o no, el bloque "finalmente" está garantizado para ejecutarse.

  1. Si el bloque "finalmente" se está ejecutando después de que se haya producido una excepción en el bloque try,

  2. y si esa excepción no se maneja

  3. y si el bloque finalmente arroja una excepción

Entonces se pierde la excepción original que ocurrió en el bloque try.

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

Gran artículo para detalles


-1

Lanza una excepción;) Puede capturar esa excepción en alguna otra cláusula catch.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.