【问题标题】:Why does stack get truncated in Exception.StackTrace?为什么堆栈在 Exception.StackTrace 中被截断?
【发布时间】:2010-12-15 15:55:44
【问题描述】:

为什么堆栈的高位部分(在 Exception.StackTrace 中)被截断? 我们来看一个简单的例子:

public void ExternalMethod()
{
  InternalMethod();
}

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

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

至于解决方案(对于那些不知道的人),据我了解有两种通用解决方案:
1) 我们可以记录静态的 Environment.StackTrace 属性,该属性包含整个堆栈(例如,从最上层(消息队列)开始,到发生异常的最深方法结束)。
2) 我们必须在最高级别捕获和记录异常。当我们需要在较低级别捕获异常以执行某项操作时,我们需要将其重新抛出(在 C# 中使用“throw”语句)。

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

【问题讨论】:

  • 一个对象为什么要关心谁调用了它?您的观点 (2),应该重新抛出异常,是正确的方法。
  • 主要堆栈跟踪信息包含在异常中,用于调试目的。对于现有的设计,它对调试的帮助并不像如果它也有更高的堆栈部分一样。因为,我再说一遍,知道谁调用了该方法对于调试非常有用。

标签: c# .net exception architecture frameworks


【解决方案1】:

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

输出:

在 System.Environment.get_StackTrace() 在 C:\Users\sam\Desktop\Source 中的 ConsoleApplication15.SuperException..ctor() \ConsoleApplication15\ConsoleApplication15\Program.cs:第 17 行 在 C:\Users\sam\Desktop\Source\Cons 中的 ConsoleApplication15.Program.ThrowIt() oleApplication15\ConsoleApplication15\Program.cs:第 49 行 在 C:\Users\sam\Desktop\Sour 中的 ConsoleApplication15.Program.InternalMethod() ce\ConsoleApplication15\ConsoleApplication15\Program.cs:第 41 行 在 C:\Users\sam\Desktop\S 中的 ConsoleApplication15.Program.Main(String[] args) ource\ConsoleApplication15\ConsoleApplication15\Program.cs:第 55 行 在 System.AppDomain._nExecuteAssembly(程序集程序集,字符串 [] 参数) 在 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext,C ontextCallback 回调,对象状态) 在 System.Threading.ThreadHelper.ThreadStart()

不可能将此行为注入到现有的系统定义的异常中,但 .Net 具有丰富的基础架构用于包装异常和重新抛出,因此它应该不是什么大问题。

【讨论】:

  • 这是另一种方法;但是,它不会指示异常是在哪里引发的,而是在哪里构造的(通常是同一件事,所以这还不错)。如果您走这条路线,请意识到它在调用上下文中会不准确(与远程处理一样),并且还需要额外的代码来序列化“fullTrace”属性。
【解决方案2】:

好的,现在我明白你的意思了......对不起,我对内联的事情感到困惑。

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

因此,如果您想在 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();
        }
    }

【讨论】:

    【解决方案3】:

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

    您可以使用以下属性来装饰您不想内联的方法:

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

    【讨论】:

    • 不,这不是因为内联。我所说的行为是默认行为。你可以自己看看。它将永远像我展示的那样。
    • 为什么 JIT 不总是以可预测的方式内联方法?也许你的方法总是被内联。
    • 它在你的函数中总是这样的事实并不能证明它不是由于内联。
    • 对不起 -1 但这不是原因。
    【解决方案4】:

    我知道,如果您在 catch 块中执行throw ex;,它会截断该点的堆栈跟踪。 throw 可能是“设计使然”,因为 throw; 不会在 catch 中截断堆栈。由于您抛出了一个新异常,因此这里可能会发生同样的情况。

    如果您导致实际异常(即int i = 100/0;)会发生什么?堆栈跟踪是否仍然被截断?

    【讨论】:

    • throw; 也会截断堆栈
    猜你喜欢
    • 1970-01-01
    • 2015-06-07
    • 2012-01-29
    • 2011-08-20
    • 1970-01-01
    • 2015-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多