代码之家  ›  专栏  ›  技术社区  ›  nightcoder

为什么在Exception.StackTrace中堆栈会被截断?

  •  11
  • nightcoder  · 技术社区  · 15 年前

    让我们看一个简单的例子:

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

    看起来这是“设计的”。但如此奇怪的设计是什么原因呢?这只会使调试变得更复杂,因为在日志消息中,我无法理解谁调用了InternalMethod(),而且通常这些信息是非常必要的。


    1) 我们可以记录static Environment.StackTrace属性,它包含整个堆栈(例如,从hiest级别(消息队列)开始,到发生异常的最深方法结束)。

    但问题是这种设计的原因。

    4 回复  |  直到 15 年前
        1
  •  11
  •   csharptest.net    15 年前

    好吧,现在我明白你的意思了。。。对不起,我对内联的事情有点困惑。

    捕获的异常中的“堆栈”只是从当前执行的catch块到引发异常的位置的增量。从概念上讲,这个行为是正确的,因为Exception.StackTrack告诉您异常在这个try/catch块的上下文中发生的位置。这允许异常堆栈跨“虚拟”调用转发,并且仍然保持准确性。一个典型的例子就是.Net远程处理异常。

    因此,如果您希望catch块中有一个完整的堆栈报告,您可以将当前堆栈添加到异常的堆栈中,如下例所示。唯一的问题是这样会更贵。

        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();
            }
        }
    
        2
  •  2
  •   Sam Saffron James Allen    15 年前

    正如csharptest所说,这是设计的。StackTrace在try块处停止。此外,框架中没有在抛出异常时调用的钩子。

    因此,您所能做的最好的事情就是沿着这些线进行,这是获得完整堆栈跟踪(在创建异常时存储完整跟踪)的绝对要求:

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

    输出:

     
         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()
    
    

    不可能将这种行为注入到现有的系统定义的异常中,但是.NET有一个丰富的基础结构来包装异常并重新抛出,所以它不应该是一个巨大的交易。

        3
  •  0
  •   JamesEggers    15 年前

    我知道如果你这样做的话 throw ex; throw; 不会在捕获中截断堆栈。这可能是因为你抛出了一个新的异常。

    如果你导致了一个实际的异常(即。 int i = 100/0; )? 堆栈跟踪是否仍被截断?

        4
  •  -1
  •   csharptest.net    15 年前

    这通常是由编译器优化引起的。

    可以使用以下属性修饰不希望内联的方法:

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