显示重新抛出的异常的堆栈跟踪,而不是抛出点的堆栈跟踪。

3

我已确认在VS2005中出现了相同的行为,因此我错误地称它为.NET(1.1)的错误。

我将保留原始问题,但我的修改后的问题是:如何使Visual Studio在调用堆栈窗口中给出我捕获并重新抛出的异常的堆栈跟踪,而不仅仅显示从throw语句点开始的调用堆栈?

情况是我在运行时决定全局异常处理程序是否开启--如果关闭,我希望VS捕获异常,以便我可以逐步查看调用堆栈以找出出错原因。

以前,全局异常处理程序要么编译进程序中,要么不编译。但情况已经改变,现在我们需要在运行时决定--看起来我可能需要回到使用宏的方式,但没有宏:

if (allow_bubble_up)
{
    Foo();
}
else
{
    try
    {
        Foo();
    }
    catch (Exception e)
    {
       GlobalExceptionHandler(e);
    }
}

但是对于我来说,那种方法感觉极其不符合DRY原则。


显然,在.NET 1.1中有一个bug,即如果你使用空的throw语句重新抛出已捕获的异常,那么堆栈跟踪将从该throw语句所在的位置开始,而不是重新抛出整个异常的堆栈跟踪--至少我在几个博客上看到它被称为一个bug,但我无法获得更多的信息。

更具体地说,在QuickWatch中,$exceptionStackTrace属性显示了正确的数据,但是在VS的调用堆栈窗口中,只显示到throw语句的级别。

在此示例代码中,我只能看到Main的一个深度为1级的堆栈跟踪,尽管我应该能够看到对Foo的几次调用的堆栈跟踪。

static public void Foo(int i)
{
    if (i > 4)
    {
        throw new ArgumentOutOfRangeException();
    }
    Foo(i + 1);
}

static void Main(string[] args)
{
    bool allow_bubble_up = true;
    try
    {
        Foo(0);
    }
    catch (Exception e)
    {
        if (allow_bubble_up)
        {
            // stack trace just shows Main
            throw;

            // also just shows Main
            //throw new Exception("asdf", e);

            // STILL just shows Main
            //throw e;
        }
        else
        {
            System.Console.WriteLine(e);
        }
    }
}

Fabrice Marguerie's blog介绍了如何解决.NET 2.0+的某些类型的重新抛出堆栈跟踪问题,在底部他建议查看Chris Taylor的博客以了解如何在.NET 1.1中实现。我不得不搜索一下archive.org才找到它。我认为我已经正确实现了它,但我仍然只在主线程上收到一个堆栈跟踪--他的解释并不是非常清楚,而且我希望尽可能少地对代码库进行更改(在另一个方法中包装现有的功能集)。

我可以在捕获和重新抛出异常的属性中看到正确的堆栈跟踪,但VS显示的可导航堆栈跟踪是无用的,因为它仅从throw语句跟踪。如果我从未捕获和重新抛出异常,我会得到完整和正确的堆栈跟踪。

如何在VS中显示正确的堆栈跟踪? 我希望有一种简单的解决方法,可能是我搜索了错误的术语。

不幸的是,这必须是VS2003 + C#。

如果不清楚,请参见以下截图(您可能需要右键单击并查看图像):

alt text http://img257.imageshack.us/img257/1124/40727627.png


1
我记得在1.1版本中这个问题非常令人沮丧。当时从未寻找解决方案,但你并没有疯。我知道你的意思!但是,仍然不如想象中使用1.1版本那么令人沮丧! - Simon_Weaver
这看起来是正确的,因为Main是调用Foo的函数,最终抛出了异常。 - user7116
@Mark Rushakoff:我已经强调了调用堆栈窗口的问题,这是最令人困惑的问题。 - user7116
5个回答

4

在Visual Studio中,它将显示程序停止的位置的调用堆栈。

如果遇到未处理的异常,程序将停在抛出异常的地方。也就是说,你的“throw”语句的位置。但是,如果你的代码已经处理了异常,Visual Studio会认为你知道该如何处理,因此忽略该异常。只有当异常在Main()中重新抛出时,它才会捕获异常,因为程序在这里没有处理异常。

如果你想在Visual Studio中捕获原始异常,有两个选项:

  • 不要在你的代码中捕获异常。默认情况下,Visual Studio仅在遇到未处理的异常时停止程序。当然,这意味着你的程序在运行时不能处理异常,因此并不是一种很有用的方法!

  • 使用你的代码捕获和重新抛出异常(就像你正在做的那样),但要配置Visual Studio在首次抛出内部异常时停止程序。打开调试> 异常,勾选“Common Language Runtime Exceptions”复选框(以停止任何异常)或浏览子树以为特定异常启用异常捕获(提示:如果你知道异常名称,请点击查找按钮并输入部分名称,例如“FileNotFound”,以快速找到异常)。这将使VS在内部异常处停止,仅在检查异常细节后选择继续执行才会移动到你的catch{}语句。


这是答案。精湛的。纯粹是精湛的。 - djmc

3

您可以抛出一个新异常,将异常e作为内部异常。然后读取内部异常的堆栈跟踪。


1
我可以很好地把堆栈跟踪读取为字符串,但当我需要浏览调用堆栈以找出错误原因时,我只能手动查找每个调用堆栈中的文件和行号。 - Mark Rushakoff
那么,Visual Studio缺少什么应该有的功能呢?我想这就是我的问题。 - user7116

3

原来,只要你知道正确的搜索术语,我试图解决的问题就有了答案。在MSIL中,它被称为异常过滤,在VS2003中可用

在Visual Basic.NET中,有一个叫做“catch-when”的结构,只有在给定谓词通过时才会执行catch。这篇MSDN博客有一个很好的例子,展示了VB.NET中catch-when如何工作,与C#的catch-throw(像我的)的结果不同。

最后,MSDN有一个工具叫做Exception Filter Inject,可以用于“为不支持异常筛选器的语言(如C#)提供异常筛选器支持”-问题在于它运行在现有的程序集上,如果你最终使用它,它会引入构建过程中的尴尬阶段。
在我发现异常过滤器注入之前,我最终实现了一个短函数,它接受“功能”委托和“catch”委托,并且仅在允许异常冒泡时调用功能,否则在try-catch中调用功能,捕获异常时调用catch委托。
我想做的是,在运行时设置要捕获的异常类型 - 如果允许异常冒泡,我将尝试捕获永远不会被调用的子类异常,否则我将只捕获基本异常。 我真的不确定在.NET 1.1中是否可能实现这一点,因为这基本上需要通用 -- 但可能可以通过反射实现,我只是没有深入研究。

1

如果我理解您的信息正确,那么在错误的堆栈跟踪和特定时间点的当前调用堆栈之间存在混淆(也许不是您造成的,而是其他人读取您的信息)。

然而,一旦进入异常处理程序,Foo例程已经完成,因此我无法看到它如何成为当前调用堆栈的一部分。

除了启用“在第一个异常处中断”外,我看不出这将如何工作,并且我不知道VS2003或VS2005中是否有任何可以帮助此问题的东西。(也许VS2010中的新调试/重放功能可以)


关于重新抛出异常的堆栈跟踪,我有些困惑(主要是我的问题)。异常的 StackTrace 属性是正确的,但我希望 VS 能够中断并允许我通过调用堆栈进行导航,就好像我根本没有捕获异常一样。我理解你关于 Foo 已经完成且临时变量不再存在的观点。不过,我可能需要将 try-catch 放在 if 语句的一个单独分支中,就像我在问题顶部的编辑中提到的那样。现在,如果有人知道另一种方法,我会保持开放的态度。 - Mark Rushakoff

1

您所描述的是调用堆栈窗口的预期行为。当Visual Studio由于未处理的异常而在throw行处中断时,呈现从throw行开始的调用堆栈是正确的。

归根结底,这取决于Visual Studio的调用堆栈窗口不知道您的异常中包含的堆栈跟踪。


+1,我曾错误地认为空的throw会像我根本没有捕获异常一样。我会保持问题开放一段时间,看看是否有人知道欺骗VS以这种方式运作的方法,否则我可能会接受你的答案。 - Mark Rushakoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接