Gettare VS rethrow: stesso risultato?
-
30-09-2019 - |
Domanda
riferendosi a un sacco di documentazione in rete, in particolare su SO, ad esempio: Qual è il modo corretto di ri-generare un'eccezione in C #? ci dovrebbe essere una differenza tra "tiro e;" e "buttare;".
Ma, da: http://bartdesmet.net /blogs/bart/archive/2006/03/12/3815.aspx ,
questo codice:
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;
}
}
}
dà il seguente risultato:
$ /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()
che è in totale contrasto con il post sul blog.
Lo stesso tipo di risultato si ottiene con il codice: http://crazorsharp.blogspot.com/2009/08/rethrowing-exception-without-resetting.html
domanda iniziale : che cosa sto facendo di sbagliato
Aggiorna : stesso risultato con .Net 3.5 / csc.exe 3.5.30729.4926
SUMUP :. Tutte le vostre risposte sono stati grandi, grazie ancora
Quindi la ragione è effettivamente inlining causa delle oscillazioni a 64 bit.
Ho dovuto scegliere una sola risposta, e qui è il motivo per cui ho scelto LukeH Risposta:
-
ha indovinato il problema inlining e il fatto che può essere correlato alla mia architettura a 64 bit,
-
ha fornito la bandiera NoInlining che è il modo più semplice per evitare questo comportamento.
Tuttavia, questo problema ora sorge un'altra domanda: questo comportamento compatibile con tutte le specifiche .Net: quelli CLR e quelli linguaggio di programmazione C #
Aggiorna : questa ottimizzazione sembra secondo conforme a: Gettare VS rethrow: stesso risultato (grazie 0xA3 )
Grazie in anticipo per il vostro aiuto.
Soluzione
Non riesco a replicare il problema -. Usando .NET 3.5 (32-bit) mi dà gli stessi risultati descritti nell'articolo di Bart
La mia ipotesi è che il .NET 4 compilatore / jitter - o forse è il 64-bit compilatore / jitter se questo sta accadendo sotto 3,5 troppo - è il metodo inlining BadGuy
sui metodi di chiamata. Prova ad aggiungere il seguente MethodImpl
attributo a BadGuy
e vedere se questo fa alcuna differenza:
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static void BadGuy()
{
//
// Some nasty behavior.
//
throw new Exception();
}
Altri suggerimenti
Ho provato a fare funzionare questo codice me stesso e le opere di debug come mi aspettavo, ma ho ottenuto lo stesso risultato nella build di rilascio.
Ho il sospetto che quello che sta succedendo è che il compilatore inline ha semplicemente sostituito la chiamata ImbrattatatoreSiti () con lancio new Exception();
perché questa è l'unica affermazione in ImbrattatatoreSiti ().
Se si spegne l'opzione 'codice Ottimizzare' nelle proprietà del progetto -.> Costruzione dello schermo, quindi sia di rilascio e di debug produce lo stesso risultato che mostra Badguy () nella parte superiore della traccia dello stack
Sembra che gli ottimizzatori di JIT fa un certo lavoro qui. Come si può vedere, lo stack di chiamate nel secondo caso è diverso da quello nel primo caso quando si esegue la build di debug. Tuttavia, nella build di rilascio, entrambi stack di chiamate sono identiche a causa di ottimizzazione.
Per vedere che questo è legato al jitter è possibile decorare i metodi con MethodImplAttribute
attributo:
[MethodImpl(MethodImplOptions.NoOptimization)]
private static void ThrowWithoutVariable()
{
try
{
BadGuy();
}
catch
{
throw;
}
}
Si noti che l'IL è ancora diverso per ThrowWithoutVariable
e 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
Aggiorna per rispondere alla tua domanda di follow-up se questo è conforme alla specifica CLI
Infatti conforme, cioè per consentire il compilatore JIT per abilitare ottimizzazioni importanti. allegato F stati a pagina 52 (enfasi da me):
Alcune istruzioni CIL eseguono implicita controlli in fase di esecuzione che assicurano la memoria e digitare sicurezza. In origine, il CLI garantiti che le eccezioni erano preciso , il che significa che lo stato del programma è stata preservata quando un'eccezione era gettato. Tuttavia, far rispettare precisi eccezioni per assegni implicite marche alcune ottimizzazioni importanti praticamente impossibile da applicare. I programmatori possono ora dichiarare, tramite un attributo personalizzato, che è un metodo “Rilassato”, che dice che le eccezioni derivanti dai controlli in fase di esecuzione implicite non deve essere preciso.
controlli Relaxed preservare verificabilità (preservando memoria e sicurezza tipo) mentre ottimizzazioni permettendo che riordino Istruzioni. In particolare, essa permette le seguenti ottimizzazioni:
- sollevamento implicita run-time out controlli di loop.
- iterazioni del ciclo Riordinamento (Ad esempio, vettorizzazione e automatico multithreading)
- Passaggio da loop
- Inlining che fa un inline Procedimento almeno veloce come il macro equivalente
Utilizzare una build di debug e vedrete la differenza in modo più chiaro. Con una build di debug la prima esecuzione mostrerà la posizione come la linea throw ex
e la seconda come proveniente dalla chiamata effettiva al BadGuy
. Ovviamente il 'problema' è la chiamata a ImbrattatatoreSiti -. Non il tiro dell'ex linea e ti inseguono un minor numero di fantasmi con la dichiarazione throw;
diretta ??p>
In una pila tracciare questa superficiale i benefici non sono così dispensati evidenti, in modo molto profondo si impilare ti mascherare la vera fonte del problema e perdere un po 'la fedeltà lanciando manualmente eccezione invece di utilizzare il costruito nel ri-lancio comunicato.
Una nota a parte, ho trovato un hack pubblicato su un blog, una volta (Da allora ho perso il riferimento), che permette di mantenere il call-stack su rethrow. Questo è particolarmente utile se si cattura un'eccezione in un contesto (ad esempio, in un thread in esecuzione un'operazione asincrona) e si desidera rigenerare in un altro (per esempio, in altro thread che ha lanciato l'operazione asincrona). Si avvale di alcune funzionalità non documentate incluso per permettere la conservazione delle tracce dello stack attraverso servizi remoti confini.
//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;
}