¿Cómo puedo volver a lanzar una excepción interna mientras mantengo el seguimiento de la pila generado hasta ahora?

StackOverflow https://stackoverflow.com/questions/1009762

Pregunta

Duplicado de: En C #, cómo ¿puedo volver a lanzar InnerException sin perder el seguimiento de la pila?

Tengo algunas operaciones que invoco de forma asíncrona en un subproceso en segundo plano. A veces, las cosas van mal. Cuando esto sucede, tiendo a obtener una TargetInvocationException, que, si bien es apropiada, es bastante inútil. Lo que realmente necesito es la InnerException de TargetInvocationException, como esta:

    try
    {
        ReturnValue = myFunctionCall.Invoke(Target, Parameters);
    }
    catch (TargetInvocationException err)
    {
        throw err.InnerException;
    }

De esa manera, mis llamadas reciben la excepción REAL que ocurrió. El problema es que la instrucción throw parece restablecer el seguimiento de la pila. Básicamente, me gustaría volver a lanzar la excepción interna, pero mantener el seguimiento de la pila que tenía originalmente. ¿Cómo hago eso?

CLARIFICACIÓN: La razón por la que quiero solo la excepción interna es que esta clase trata de "abstraer" el hecho de que estas funciones (delegados proporcionados por la persona que llama) se ejecutan en otros hilos y demás. Si hay una excepción, lo más probable es que no tenga nada que ver con ejecutarse en un subproceso en segundo plano, y al llamador realmente le gustaría el seguimiento de la pila que entra en su delegado y encuentra el problema real, no mi llamada para invocar.

¿Fue útil?

Solución

Es posible preservar el rastro de la pila antes de volver a lanzar sin reflexión:

static void PreserveStackTrace (Exception e)
{
    var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ;
    var mgr = new ObjectManager     (null, ctx) ;
    var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ;

    e.GetObjectData    (si, ctx)  ;
    mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData
    mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData

    // voila, e is unmodified save for _remoteStackTraceString
}

Esto desperdicia muchos ciclos en comparación con InternalPreserveStackTrace, pero tiene la ventaja de depender solo de la funcionalidad pública. Aquí hay un par de patrones de uso comunes para las funciones de preservación de seguimiento de pila:

// usage (A): cross-thread invoke, messaging, custom task schedulers etc.
catch (Exception e)
{
    PreserveStackTrace (e) ;

    // store exception to be re-thrown later,
    // possibly in a different thread
    operationResult.Exception = e ;
}

// usage (B): after calling MethodInfo.Invoke() and the like
catch (TargetInvocationException tiex)
{
    PreserveStackTrace (tiex.InnerException) ;

    // unwrap TargetInvocationException, so that typed catch clauses 
    // in library/3rd-party code can work correctly;
    // new stack trace is appended to existing one
    throw tiex.InnerException ;
}

Otros consejos

No, eso no es posible. Su única oportunidad real es seguir el patrón recomendado y lanzar su propia excepción con la InnerException apropiada.

Editar

Si le preocupa la presencia de TargetInvocationException y desea ignorarlo (no es que lo recomiendo, ya que podría muy bien tener algo que ver con el hecho de que se está ejecutando en otro hilo), entonces nada le impide lanzar su propia excepción aquí y adjuntar la InnerException de la TargetInvocationException como su propia InnerException . Es un poco maloliente, pero puede lograr lo que quieres.

Hay una manera de "restablecer" el seguimiento de la pila en una excepción mediante el uso del mecanismo interno que se utiliza para preservar los seguimientos de la pila del lado del servidor cuando se usa la comunicación remota, pero es horrible:

try
{
    // some code that throws an exception...
}
catch (Exception exception)
{
    FieldInfo remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
    remoteStackTraceString.SetValue(exception, exception.StackTrace);
    throw exception;
}

Esto coloca la traza de pila original en el campo _remoteStackTraceString de la excepción, que se concatena con la traza de pila recién restablecida cuando se vuelve a lanzar la excepción.

Este es realmente un truco horrible , pero logra lo que quieres. Sin embargo, está jugando dentro de la clase System.Exception , por lo que este método puede romperse en versiones posteriores del marco.

Aunque puede sentir que TargetInvocationException es "inútil", es la realidad. No intente fingir que .NET no tomó la excepción original y la envolvió con una TargetInvocationException y la arrojó. Eso realmente sucedió. Algún día, incluso podría desear alguna información que provenga de esa envoltura, como tal vez la ubicación del código que arrojó la TargetInvocationException.

No puedes hacer eso. throw siempre restablece el seguimiento de la pila, a menos que se use sin parámetro. Me temo que las personas que llamen tendrán que usar InnerException ...

Uso de " throw " la palabra clave con una excepción siempre restablecerá el seguimiento de la pila.

Lo mejor que puedes hacer es capturar la excepción real que deseas y usar " throw; " en lugar de " tirar ex; " ;. O para lanzar su propia excepción, con la InnerException que desea transmitir.

No creo que lo que quieras hacer sea posible.

Como otros han dicho, use el "tiro" palabra clave sin agregarla para mantener intacta la cadena de excepción. Si necesita esa excepción original (suponiendo que eso es lo que quiere decir), puede llamar a Exception.GetBaseException () al final de su cadena para obtener la Excepción que lo inició todo.

Es posible con .net 4.5:

catch(Exception e)
{
   ExceptionDispatchInfo.Capture(e.InnerException).Throw();
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top