2009-11-11 15 views
10

Perché la parte alta dello stack (in Exception.StackTrace) viene troncata? Vediamo un semplice esempio:Perché lo stack viene troncato in Exception.StackTrace?

public void ExternalMethod() 
{ 
    InternalMethod(); 
} 

public void InternalMethod() 
{ 
    try 
    { 
    throw new Exception(); 
    } 
    catch(Exception ex) 
    { 
    // ex.StackTrace here doesn't contain ExternalMethod()! 
    } 
} 

Sembra che questo è "by design". Ma quali sono le ragioni di un disegno così strano? Rende il debug più complesso, perché nei messaggi di log non riesco a capire chi ha chiamato InternalMethod() e spesso questa informazione è molto necessaria.

Per quanto riguarda le soluzioni (per coloro che non sanno), ci sono 2 soluzioni generali a quanto ho capito:
1) Siamo in grado di accedere proprietà Environment.StackTrace statica, che contiene l'intero stack (per esempio, a partire da il livello più alto (coda messaggi) e termina con il metodo più profondo in cui si verifica l'eccezione).
2) Dobbiamo catturare e registrare le eccezioni ai livelli più alti. Quando dobbiamo catturare le eccezioni a livelli più bassi per fare qualcosa, abbiamo bisogno di rilanciare (con l'istruzione "lancia" in C#) più avanti.

Ma la domanda riguarda le ragioni di tale progettazione.

+0

Perché un oggetto dovrebbe interessarsi a chi lo ha chiamato? Il tuo punto (2), nel senso che l'eccezione dovrebbe essere rigettata, è l'approccio corretto. –

+0

Principalmente le informazioni di tracciamento dello stack sono incluse con eccezione a scopo di debug. Con il design esistente non aiuta a fare il debug come se lo fosse se avesse anche una parte più alta dello stack. Perché, ripeto, sapere chi ha chiamato il metodo può essere molto utile per il debug. – nightcoder

risposta

0

So che in un blocco catch se si esegue throw ex; tronca la traccia dello stack in quel punto. È possibile che sia "in base al design" per il lancio poiché solo throw; non tronca lo stack in una presa. Lo stesso potrebbe succedere qui da quando stai lanciando una nuova eccezione.

Cosa succede se si causa un'eccezione effettiva (ad esempio int i = 100/0;)? La traccia dello stack è ancora troncata?

+1

'' 'throw;' '' tronca anche stack – Enerccio

-1

Ciò è spesso causato dalle ottimizzazioni del compilatore.

Potete decorare i metodi che non si desidera inline utilizzando il seguente attributo:

[MethodImpl(MethodImplOptions.NoInlining)] 
public void ExternalMethod() 
{ 
    InternalMethod(); 
} 
+0

No, questo non è dovuto all'inlinazione. Il comportamento che ho dichiarato è il comportamento predefinito. Puoi vederlo da solo. Sarà sempre come ho mostrato. – nightcoder

+1

Perché il JIT non dovrebbe sempre allineare il metodo in modo prevedibile? Forse il tuo metodo è sempre in linea. –

+0

Il fatto che sia sempre così nella tua funzione non prova in alcun modo che non sia dovuto alla funzione di inlining. –

11

Ok, ora vedo che cosa il vostro andare a parare ... Ci scusiamo per la mia confusione sulla cosa inline.

Lo 'stack' in un'eccezione rilevata è solo un delta dal blocco catch attualmente in esecuzione a cui è stata generata l'eccezione. Concettualmente questo comportamento è corretto in quanto Exception.StackTrack ti dice dove si è verificata l'eccezione nel contesto di questo blocco try/catch. Ciò consente agli stack di eccezioni di essere inoltrati attraverso chiamate "virtuali" mantenendo comunque l'accuratezza. Un classico esempio di ciò che viene fatto è .Net Remoting exceptions.

Pertanto, se si desidera un report di stack completo nel blocco catch, si aggiungerà lo stack corrente allo stack dell'eccezione come nell'esempio seguente. L'unico problema è che questo può essere più costoso.

private void InternalMethod() 
    { 
     try 
     { 
      ThrowSomething(); 
     } 
     catch (Exception ex) 
     { 
      StackTrace currentStack = new StackTrace(1, true); 
      StackTrace exceptionStack = new StackTrace(ex, true); 
      string fullStackMessage = exceptionStack.ToString() + currentStack.ToString(); 
     } 
    } 
+0

+1 questo è il motivo, vedere la mia risposta per una soluzione alternativa –

2

Come csharptest detto questo è di progettazione. StackTrace si ferma al blocco try. Inoltre, non c'è nessun hook nel framework che viene chiamato quando viene lanciata un'eccezione.

Quindi la cosa migliore che puoi fare è qualcosa in questo senso, è la sua un requisito assoluto per ottenere tracce stack completo (conservare una traccia completa sulla creazione di eccezioni):

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Runtime.CompilerServices; 
using System.Diagnostics; 

namespace ConsoleApplication15 { 

    [global::System.Serializable] 
    public class SuperException : Exception { 

     private void SaveStack() { 
      fullTrace = Environment.StackTrace; 
     } 

     public SuperException() { SaveStack(); } 
     public SuperException(string message) : base(message) { SaveStack(); } 
     public SuperException(string message, Exception inner) : base(message, inner) { SaveStack(); } 
     protected SuperException(
      System.Runtime.Serialization.SerializationInfo info, 
      System.Runtime.Serialization.StreamingContext context) 
      : base(info, context) { } 

     private string fullTrace; 
     public override string StackTrace { 
      get { 
       return fullTrace; 
      } 
     } 
    } 

    class Program { 

     public void ExternalMethod() { 
      InternalMethod(); 
     } 

     public void InternalMethod() { 
      try { 
       ThrowIt(); 
      } catch (Exception ex) { 
       Console.WriteLine(ex.StackTrace); 
      } 
     } 

     [MethodImpl(MethodImplOptions.NoInlining)] 
     public void ThrowIt() { 
      throw new SuperException(); 
     } 


     static void Main(string[] args) { 
      new Program().ExternalMethod(); 
      Console.ReadKey(); 
     } 
    } 
} 

Uscite:

 
    at System.Environment.get_StackTrace() 
    at ConsoleApplication15.SuperException..ctor() in C:\Users\sam\Desktop\Source 
\ConsoleApplication15\ConsoleApplication15\Program.cs:line 17 
    at ConsoleApplication15.Program.ThrowIt() in C:\Users\sam\Desktop\Source\Cons 
oleApplication15\ConsoleApplication15\Program.cs:line 49 
    at ConsoleApplication15.Program.InternalMethod() in C:\Users\sam\Desktop\Sour 
ce\ConsoleApplication15\ConsoleApplication15\Program.cs:line 41 
    at ConsoleApplication15.Program.Main(String[] args) in C:\Users\sam\Desktop\S 
ource\ConsoleApplication15\ConsoleApplication15\Program.cs:line 55 
    at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) 
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C 
ontextCallback callback, Object state) 
    at System.Threading.ThreadHelper.ThreadStart() 

Non è possibile iniettare questo comportamento nelle eccezioni definite dal sistema esistenti, ma .Net ha una ricca infrastruttura per il wrapping di eccezioni e rethrowing quindi non dovrebbe essere un grosso problema.

+1

Questo è un approccio alternativo; tuttavia, non indicherà dove viene lanciata l'eccezione, ma dove è stata costruita (di solito la stessa cosa quindi non è male). Se stai percorrendo questa strada, rendi conto che sarà inaccurato tra i contesti delle chiamate (come con i servizi remoti) e che avrà anche bisogno di codice aggiuntivo per serializzare la proprietà 'fullTrace'. –

+0

spot sul commento. –