通常当我抛出一个异常,抓住它并打印出堆栈跟踪时,我会看到抛出异常的调用,导致该调用的调用,导致的调用,等等,回到整个程序的根源。
现在它只显示异常被捕获的调用,而不是显示抛出的位置。我无法弄清楚改变了什么导致了这一点。这是我的计划:
using System;
class foo {
static void Main(string[] args) {
try { f(); }
catch (Exception e) { Console.WriteLine(e.StackTrace); }
}
static void f() { g(); }
static void g() { throw new Exception(); }
}
以下是打印出来的内容:
at foo.Main(String[] args) in C:\Projects\test\root.cs:line 5
我的期望是这样的:
at foo.g...
at foo.f...
at foo.Main...
有什么想法吗?
答案 0 :(得分:20)
我将借此机会再次声明 - 正如我经常做的那样 - 堆栈跟踪不告诉你调用了什么方法来到达异常点被抛出。类似地,调试器中的堆栈窗口不会告诉您哪种方法称为当前方法。
相反,在这两种情况下,堆栈跟踪都会告诉您控件将在何处进行,或者,在异常的情况下,显然控制将消失接下来没有例外。堆栈跟踪是延续;它是一个描述程序的 future 执行的数据结构。它不一定描述过去。
“你将要去哪里”的事实几乎总是与“我来自哪里”的事实相同,是什么使得堆栈跟踪成为一种有用的调试工具。但是如果运行时可以安全地消除该信息而不会弄乱“我需要去哪里”的信息,那么堆栈跟踪不需要包含任何关于“我来自哪里”的信息。在某些情况下,运行时可以并确实消除了该信息。 你不能依靠堆栈跟踪告诉你他们来自哪里,只有你下一步的地方。
在下一版本的C#和VB中,“async / await”只会加剧这种情况;在通过连续传递样式实现异步方法的世界中,不需要将堆栈作为延续,因为continuation以委托的形式存储在堆上。在这个世界上,堆栈跟踪几乎不会告诉你“我从哪里来的?”
答案 1 :(得分:10)
它仍然显示抛出异常的位置,而不是它被捕获的位置。但是由于优化可能不是你期望它被抛出的地方。
最有可能抛出异常的函数被内联到调用函数中。
在这种情况下,我希望JIT优化器负责。默认情况下,它不会在调试器中运行时进行优化,但会在未在调试器中运行时优化并因此内联方法。我不确定Debug构建是否会对内联产生影响。
如果将[MethodImpl(MethodImplOptions.NoInlining)]
添加到函数中,它将不会内联,并且堆栈跟踪应该符合预期。
答案 2 :(得分:3)
如果这是一个未附加调试器的发布版本,则JIT可能正在通过inlining方法进行优化;所以f
和g
甚至不存在。
您可以通过将[MethodImpl(MethodImplOptions.NoInlining)]
属性应用于方法g
和f
来确认这是发生了什么,但这仅用于教育目的。您不应该停止JIT内联生产代码。
此外,x64 JIT may optimize将其作为尾调用,因此MethodImplAttribute
可能对x64版本构建没有影响。
答案 3 :(得分:2)
来自MSDN的文档:
“由于优化期间发生的代码转换(例如内联),StackTrace属性可能无法报告尽可能多的方法调用。”