2009-06-17 15 views
19

Duplicato di: In C#, how can I rethrow InnerException without losing stack trace?Come posso rilanciare un'eccezione interna mantenendo la traccia dello stack generata finora?

Ho alcune operazioni che richiamo in modo asincrono su un thread in background. A volte le cose vanno male. Quando ciò accade, tendo a ottenere una TargetInvocationException, che, se del caso, è abbastanza inutile. Quello che ho veramente bisogno è InnerException del TargetInvocationException, in questo modo:

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

In questo modo, i miei interlocutori sono serviti con l'eccezione reale che si è verificato. Il problema è che l'istruzione throw sembra resettare la traccia dello stack. Mi piacerebbe fondamentalmente ripensare all'eccezione interna, ma mantenere traccia dello stack originariamente. Come lo faccio?

PRECISAZIONE: Il motivo che voglio solo l'eccezione interna è che questa classe cerca di 'astrarre' tutto fatto che queste funzioni (delegati forniti dal chiamante) sono eseguiti su altri thread e quant'altro. Se c'è un'eccezione, allora le probabilità sono che non ha nulla a che fare con l'esecuzione su un thread in background, e il chiamante gradirebbe davvero la traccia dello stack che va al loro delegato e trova il vero problema, non la mia chiamata da invocare.

+0

Perché TargetInvocationException inutile? Contiene 'InnerException', quindi ha tutte le informazioni. –

+0

Risposta aggiornata, prova a lanciare la tua usando InnerException –

+0

C'è un altro modo per farlo che non richiede alcun voodoo. Dai un'occhiata alla risposta qui: http://stackoverflow.com/questions/15668334/preserving-exceptions-from-dynamically-invoked-methods –

risposta

29

E è possibile conservare la traccia stack prima rethrowing senza riflessione:

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 
} 

Questo un grande spreco di cicli rispetto al InternalPreserveStackTrace, ma ha il vantaggio di basarsi solo su funzionalità pubblica. Qui ci sono un paio di modelli di utilizzo comuni per le funzioni dello stack trace-conservazione:

// 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 ; 
} 
+1

top banana! questo è il livello di efficienza –

+1

quando si ha un'eccezione che vale la pena. Quindi, come lo hai capito? Grazie per il post! –

+0

Ottima risposta. Si noti tuttavia che 'DoFixups()' [genererà SerializationExceptions] (http://stackoverflow.com/questions/3086234), nel caso in cui una classe di eccezioni non sia deserializzabile. Si potrebbe quindi voler avvolgere il corpo di 'PreserveStackTrace' in un grande' try/catch'. –

6

No, non è possibile. La tua unica vera opportunità è seguire lo schema raccomandato e lanciare la tua eccezione con l'appropriato InnerException.

Modifica

Se la vostra preoccupazione è la presenza del TargetInvocationException e si desidera di non tenerne conto (non che io consiglierei questo, in quanto potrebbe benissimo avere qualcosa a che fare con il fatto che si tratta di essere eseguire su un altro thread) quindi nulla vi impedisce di lanciare la vostra eccezione qui e allegando il InnerException dal TargetInvocationException come il vostro InnerException. È un po 'puzzolente, ma potrebbe portare a termine quello che vuoi.

+0

It * is * possible, vedi sotto. –

+0

@Anton: Questa è sicuramente una scoperta interessante. Non mi piace molto l'idea di essere in grado di rimuovere un'eccezione esterna - anche "TargetInvocationException" - ma chiaramente c'è una parte significativa della community che lo fa. –

+1

Sì, perché spesso non c'è altro modo - la libreria e il codice di terze parti raramente capiscono "TargetInvocationException" et al., E anche nel nostro codice gestirlo ovunque potrebbe presumibilmente apparire non è la fine di un problema, cosa con tipo di eccezione verifica la duplicazione delle clausole e degli elementi di cattura. –

0

Non puoi farlo. throw reimposta sempre la traccia di stack, a meno che non venga utilizzata senza parametro. Ho paura che i chiamanti debbano utilizzare InnerException ...

0

L'utilizzo della parola chiave "throw" con un'eccezione reimposterà sempre la traccia dello stack.

La cosa migliore da fare è catturare l'effettiva eccezione che si desidera e utilizzare "lanciare"; invece di "lanciare ex;". O lanciare la propria eccezione, con InnerException che si desidera passare.

Non credo che quello che vuoi fare sia possibile.

2

Sebbene si possa ritenere che TargetInvocationException sia "inutile", è la realtà. Non provare a fingere che .NET non abbia preso l'eccezione originale e avvolgila con una TargetInvocationException e lanciala. E 'successo davvero. Un giorno, potresti persino desiderare qualche informazione che proviene da quel wrapping - come forse la posizione del codice che ha gettato TargetInvocationException.

+0

La posizione del rethrower è sempre un valore inutile in ogni pezzo che ho visto. – Joshua

5

C'è un modo di "resettare" la traccia dello stack su un'eccezione utilizzando il meccanismo interno che viene utilizzato per conservare tracce dello stack lato server quando si utilizzano i servizi remoti, ma è orribile:

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

questo mette la traccia dello stack originale nel campo _remoteStackTraceString dell'eccezione, che viene concatenata alla traccia dello stack appena ripristinata quando l'eccezione viene nuovamente generata.

Questo è davvero un attacco orribile, ma realizza ciò che desideri. Si sta armeggiando all'interno della classe System.Exception anche se questo metodo potrebbe quindi interrompere le successive versioni del framework.

+2

questo è fantastico, per quando hai * un * genuino * bisogno di ribaltare un'eccezione in un posto diverso ma mantenere la traccia del vecchio stack (come in un framework di test unitario) –

0

Come altri hanno già detto, utilizzare la parola chiave "throw" senza aggiungerla per mantenere intatta la catena di eccezioni. Se hai bisogno dell'eccezione originale (supponendo che sia ciò che intendi), puoi chiamare Exception.GetBaseException() alla fine della catena per ottenere l'eccezione che ha dato il via a tutto.

0

E 'possibile con .net 4.5:

catch(Exception e) 
{ 
    ExceptionDispatchInfo.Capture(e.InnerException).Throw(); 
} 
Problemi correlati