2010-08-23 9 views
10

haciendo referencia a una gran cantidad de documentación en la red, especialmente en SO, por ejemplo: What is the proper way to re-throw an exception in C#? debe haber una diferencia entre "throw e;" y "tirar".Throw VS rethrow: ¿el mismo resultado?

Pero, a partir de: http://bartdesmet.net/blogs/bart/archive/2006/03/12/3815.aspx,

este código:

using System; 

class Ex 
{ 
    public static void Main() 
    { 
    // 
    // First test rethrowing the caught exception variable. 
    // 
    Console.WriteLine("First test"); 
    try 
    { 
    ThrowWithVariable(); 
    } 
    catch (Exception ex) 
    { 
    Console.WriteLine(ex.StackTrace); 
    } 

    // 
    // Second test performing a blind rethrow. 
    // 
    Console.WriteLine("Second test"); 
    try 
    { 
    ThrowWithoutVariable(); 
    } 
    catch (Exception ex) 
    { 
    Console.WriteLine(ex.StackTrace); 
    } 
} 

private static void BadGuy() 
{ 
    // 
    // Some nasty behavior. 
    // 
    throw new Exception(); 
} 

    private static void ThrowWithVariable() 
{ 
    try 
    { 
     BadGuy(); 
    } 
    catch (Exception ex) 
    { 
    throw ex; 
    } 
} 

    private static void ThrowWithoutVariable() 
{ 
    try 
    { 
    BadGuy(); 
    } 
    catch 
    { 
    throw; 
    } 
    } 
} 

da el siguiente resultado:

$ /cygdrive/c/Windows/Microsoft.NET/Framework/v4.0.30319/csc.exe Test.cs 
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1 
Copyright (C) Microsoft Corporation. All rights reserved. 

$ ./Test.exe 
First test 
    at Ex.ThrowWithVariable() 
    at Ex.Main() 
Second test 
    at Ex.ThrowWithoutVariable() 
    at Ex.Main() 

que está en completa contradicción con la entrada de blog.

El mismo tipo de resultado se obtiene con el código de: http://crazorsharp.blogspot.com/2009/08/rethrowing-exception-without-resetting.html

pregunta original: ¿qué estoy haciendo mal?

ACTUALIZACIÓN: mismo resultado con .Net 3.5/csc.exe 3.5.30729.4926

SUMUP: todas sus respuestas fueron muy bien, gracias de nuevo.

Por lo tanto, la razón es efectiva debido al JITter de 64 bits.

tuviera que elegir una sola respuesta, y aquí es por eso que he elegido LukeH respuesta:

  • Supuso el problema procesos en línea y el hecho de que puede estar relacionado con mi arquitectura de 64 bits,

  • proporcionó la bandera NoInline, que es la forma más sencilla de evitar este comportamiento.

Sin embargo este número se eleva ahora otra pregunta: es este comportamiento compatible con todas las especificaciones .Net: los CLR y los C# los lenguajes de programación?

ACTUALIZACIÓN: esta optimización parece compatible en función de: Throw VS rethrow : same result? (gracias 0xA3)

Gracias de antemano por su ayuda.

Respuesta

4

no puedo replicar el problema - el uso de .NET 3.5 (32 bits) me da los mismos resultados que se describen en el artículo de Bart.

Supongo que el compilador/jitter de .NET 4 - o tal vez es el compilador/jitter de 64 bits si esto ocurre también en 3.5 - está incorporando el método BadGuy en los métodos de llamada. Trate de añadir el siguiente atributo a MethodImplBadGuy y ver si hay alguna diferencia:

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] 
private static void BadGuy() 
{ 
    // 
    // Some nasty behavior. 
    // 
    throw new Exception(); 
} 
+0

Tiene razón, usar la bandera NoInlining hizo el truco. Gracias. – Pragmateek

1

Usa una compilación de depuración y verás la diferencia más claramente. Con una construcción de depuración, la primera ejecución mostrará la ubicación como la línea throw ex y la segunda como procedente de la llamada real al BadGuy. Obviamente, el "problema" es la llamada a BadGuy, no a la línea de saque de banda y perseguirás menos fantasmas con la instrucción directa throw;.

En un rastro de pila, estos beneficios superficiales no son tan obvios, en una pila muy profunda enmascararás la fuente real del problema y perderás cierta fidelidad al lanzar una excepción manualmente en lugar de usar el built-in incorporado lanzar declaración.

+0

Sí, es cierto, la creación de errores funciona como se esperaba. Gracias. – Pragmateek

3

He intentado ejecutar este código yo mismo y la compilación de depuración funciona como esperaba pero obtuve el mismo resultado que en la compilación de lanzamiento.

Sospecho que lo que está sucediendo es que el compilador en línea simplemente reemplazó la llamada BadGuy() con throw new Exception(); porque esa es la única declaración en BadGuy().

Si desactiva la opción 'Optimizar código' en las propiedades del proyecto -> Pantalla de compilación, tanto la versión de liberación como la de depuración producen el mismo resultado que muestra BadGuy() en la parte superior de la pila.

+0

Inlinear parece ser lo que efectivamente sucede: ¡gran conjetura! – Pragmateek

3

Parece que los optimizadores JIT funcionan aquí. Como puede ver, la pila de llamadas en el segundo caso es diferente que en el primer caso cuando ejecuta la compilación Debug. Sin embargo, en la versión Release, ambas pilas de llamadas son idénticas debido a la optimización.

ver que esto está relacionado con la fluctuación de fase se puede decorar los métodos con un atributo MethodImplAttribute:

[MethodImpl(MethodImplOptions.NoOptimization)] 
private static void ThrowWithoutVariable() 
{ 
    try 
    { 
     BadGuy(); 
    } 
    catch 
    { 
     throw; 
    } 
} 

Tenga en cuenta que la IL sigue siendo diferente para ThrowWithoutVariable y ThrowWithVariable:

.method private hidebysig static void ThrowWithVariable() cil managed 
{ 
    // Code size  11 (0xb) 
    .maxstack 1 
    .locals init ([0] class [mscorlib]System.Exception ex) 
    .try 
    { 
    IL_0000: call  void Ex::BadGuy() 
    IL_0005: leave.s IL_000a 
    } // end .try 
    catch [mscorlib]System.Exception 
    { 
    IL_0007: stloc.0 
    IL_0008: ldloc.0 
    IL_0009: throw 
    } // end handler 
    IL_000a: ret 
} // end of method Ex::ThrowWithVariable 

.method private hidebysig static void ThrowWithoutVariable() cil managed 
{ 
    // Code size  11 (0xb) 
    .maxstack 1 
    .try 
    { 
    IL_0000: call  void Ex::BadGuy() 
    IL_0005: leave.s IL_000a 
    } // end .try 
    catch [mscorlib]System.Object 
    { 
    IL_0007: pop 
    IL_0008: rethrow 
    } // end handler 
    IL_000a: ret 
} // end of method Ex::ThrowWithoutVariable 

Actualice para responder a su pregunta de seguimiento si esto es compatible con la especificación CLI

De hecho, cumple, es decir, permite que el compilador JIT permita optimizaciones importantes. Annex F estados en la página 52 (el subrayado por mí):

Algunas instrucciones CIL realizan implícitas controles en tiempo de ejecución que garanticen la memoria y seguridad de tipos. Originalmente, la CLI garantizaba que las excepciones eran precisa, lo que significa que el estado del programa se conservaba cuando se lanzaba una excepción . Sin embargo, aplicar excepciones precisas para comprobaciones implícitas hace que algunas optimizaciones importantes sean prácticamente imposibles de aplicar. Los programadores ahora pueden declarar, mediante un atributo personalizado , que un método es "relajado", que dice que las excepciones que surgen de comprobaciones de tiempo de ejecución implícitas no necesitan ser precisas.

cheques Relajado conservan verificabilidad (preservando memoria y la seguridad de tipos), mientras que optimizaciones que permiten que reordenan instrucciones. En particular, se permite las siguientes optimizaciones:

  • Levantamiento de controles en tiempo de ejecución implícitas a cabo de bucles.
  • Reordenando las iteraciones de bucle (p., Vectorización y automático multihilo)
  • bucles Intercambiar
  • Inlining que hace un método inline como menos tan rápido como el equivalente macro
+0

Downvoter, atrévete a dejar un motivo? –

+0

Gracias, el indicador NoOptimization arroja el resultado esperado en todos los casos. – Pragmateek

+0

Gracias de nuevo por su rica respuesta sobre el cumplimiento de dicha optimización. – Pragmateek

0

En una nota lateral, he encontrado un hack publicado en un blog una vez (he perdido desde entonces la referencia) que permite realizar retener la pila de llamadas en el nuevo lanzamiento. Esto es útil principalmente si detecta una excepción en un contexto (por ejemplo, en un subproceso que ejecuta una operación asincrónica) y desea volver a lanzarla en otro (por ejemplo, en el otro subproceso que inició la operación asincrónica). Utiliza algunas funciones no documentadas incluidas para permitir la conservación de los rastros de pila a través de los límites remotos.

//This terrible hack makes sure track trace is preserved if exception is re-thrown 
    internal static Exception AppendStackTrace(Exception ex) 
    { 
     //Fool CLR into appending stack trace information when the exception is re-thrown 
     var remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", 
                   BindingFlags.Instance | 
                   BindingFlags.NonPublic); 
     if (remoteStackTraceString != null) 
      remoteStackTraceString.SetValue(ex, ex.StackTrace + Environment.NewLine); 

     return ex; 
    } 
+2

No es necesario hacer eso, para eso es InnerException. 'lanzar nueva excepción (" I blow up ", ex)'. –

+2

Esto es realmente terrible y nunca debería ser usado. –

+0

@Paul, el problema con InnerException es que las cláusulas de captura se descomponen (deben detectar el tipo de excepción externa). Esto rompe la transparencia si desea que un manejador asincrónico pueda interpretar excepciones de la misma manera que si se llamara a la versión síncrona. Hay formas de evitar esto (puede hacer que la versión síncrona incluya también la excepción), pero es algo más incómodo en el lado de la captura. –

Cuestiones relacionadas