为什么.NET异常没有被try / catch块捕获?

时间:2008-08-30 14:57:13

标签: c# .net exception antlr

我正在使用C#的ANTLR解析器库开发一个项目。我已经构建了一个语法来解析一些文本,它运行良好。但是,当解析器遇到非法或意外的令牌时,它会抛出许多异常中的一个。问题是在某些情况下(并非所有)我的try / catch块都不会捕获它,而是将执行作为未处理的异常停止。

对我来说,问题是我无法在其他任何地方复制此问题,而是在我的完整代码中。调用堆栈显示异常肯定发生在我的try / catch(Exception)块中。我唯一能想到的是,在我的代码和引发异常的代码之间发生了一些ANTLR程序集调用,而且这个库没有启用调试,所以我无法单步执行它。我想知道不可调试的程序集是否会抑制异常冒泡?调用堆栈看起来像这样;外部程序集调用在Antlr.Runtime中:

    Expl.Itinerary.dll!TimeDefLexer.mTokens() Line 1213 C#
    Antlr3.Runtime.dll!Antlr.Runtime.Lexer.NextToken() + 0xfc bytes 
    Antlr3.Runtime.dll!Antlr.Runtime.CommonTokenStream.FillBuffer() + 0x22c bytes   
    Antlr3.Runtime.dll!Antlr.Runtime.CommonTokenStream.LT(int k = 1) + 0x68 bytes
    Expl.Itinerary.dll!TimeDefParser.prog() Line 109 + 0x17 bytes   C#
    Expl.Itinerary.dll!Expl.Itinerary.TDLParser.Parse(string Text = "", Expl.Itinerary.IItinerary Itinerary = {Expl.Itinerary.MemoryItinerary}) Line 17 + 0xa bytes C#

Parse()中最底部调用的代码片段如下所示:

     try {
        // Execution stopped at parser.prog()
        TimeDefParser.prog_return prog_ret = parser.prog();
        return prog_ret == null ? null : prog_ret.value;
     }
     catch (Exception ex) {
        throw new ParserException(ex.Message, ex);
     }

对我来说,catch(Exception)子句应该捕获任何异常。有什么理由不这样做吗?

更新:我使用Reflector跟踪外部程序集,发现没有任何线程的证据。该程序集似乎只是ANTLR生成的代码的运行时实用程序类。抛出的异常来自TimeDefLexer.mTokens()方法,其类型为NoViableAltException,它派生自RecognitionException - >例外。当词法分析器无法理解流中的下一个标记时,抛出此异常;换句话说,输入无效。这个例外是支持发生的,但它应该被我的try / catch块捕获。

此外,重新抛出ParserException与这种情况无关。这是一个抽象层,它在解析期间接受任何异常并转换为我自己的ParserException。我遇到的异常处理问题永远不会到达那行代码。事实上,我注释掉了“抛出新的ParserException”部分并仍然收到了相同的结果。

还有一件事,我修改了原来的try / catch块而不是捕获NoViableAltException,消除了任何继承混淆。我仍然收到了同样的结果。

有人曾经说过,在调试模式下,有时候VS在捕获处理异常方面过于活跃,但是这个问题也会在发布模式下发生。

伙计,我还是难过!我之前没有提到它,但我正在运行VS 2008,我的所有代码都是3.5。外部组件是2.0。另外,我的一些代码是2.0程序集中的类的子类。版本不匹配会导致此问题吗?

更新2:我能够通过将.NET 3.5代码的相关部分移植到.NET 2.0项目并复制相同的方案来消除.NET版本冲突。在.NET 2.0中一直运行时,我能够复制相同的未处理异常。

我了解到ANTLR最近发布了3.1。所以,我从3.0.1升级并重试。事实证明生成的代码有点重构,但在我的测试用例中会发生同样的未处理异常。

更新3: 我在simplified VS 2008 project中复制了这个场景。您可以自己下载并检查项目。我已经应用了所有伟大的建议,但还未能克服这个障碍。

如果您可以找到解决方法,请分享您的发现。再次感谢!


谢谢,但VS 2008会自动打破未处理的异常。另外,我没有Debug-> Exceptions对话框。抛出的NoViableAltException是完全有意的,旨在被用户代码捕获。由于未按预期捕获,程序执行意外停止为未处理的异常。

抛出的异常是从Exception派生的,并且ANTLR没有进行多线程。

25 个答案:

答案 0 :(得分:30)

我相信我理解这个问题。异常被捕获,问题是调试器的行为混乱以及每个试图重新调试它的人之间调试器设置的差异。

在你的repro的第三种情况下,我相信你收到以下消息:“NoViableAltException未被用户代码处理”和一个看起来像这样的callstack:

         [External Code]    
    >   TestAntlr-3.1.exe!TimeDefLexer.mTokens() Line 852 + 0xe bytes   C#
        [External Code] 
        TestAntlr-3.1.exe!TimeDefParser.prog() Line 141 + 0x14 bytes    C#
        TestAntlr-3.1.exe!TestAntlr_3._1.Program.ParseTest(string Text = "foobar;") Line 49 + 0x9 bytes C#
        TestAntlr-3.1.exe!TestAntlr_3._1.Program.Main(string[] args = {string[0x00000000]}) Line 30 + 0xb bytes C#
        [External Code] 

如果右键单击callstack窗口并运行show show external code,您会看到:

        Antlr3.Runtime.dll!Antlr.Runtime.DFA.NoViableAlt(int s = 0x00000000, Antlr.Runtime.IIntStream input = {Antlr.Runtime.ANTLRStringStream}) + 0x80 bytes   
        Antlr3.Runtime.dll!Antlr.Runtime.DFA.Predict(Antlr.Runtime.IIntStream input = {Antlr.Runtime.ANTLRStringStream}) + 0x21e bytes  
    >   TestAntlr-3.1.exe!TimeDefLexer.mTokens() Line 852 + 0xe bytes   C#
        Antlr3.Runtime.dll!Antlr.Runtime.Lexer.NextToken() + 0xc4 bytes 
        Antlr3.Runtime.dll!Antlr.Runtime.CommonTokenStream.FillBuffer() + 0x147 bytes   
        Antlr3.Runtime.dll!Antlr.Runtime.CommonTokenStream.LT(int k = 0x00000001) + 0x2d bytes  
        TestAntlr-3.1.exe!TimeDefParser.prog() Line 141 + 0x14 bytes    C#
        TestAntlr-3.1.exe!TestAntlr_3._1.Program.ParseTest(string Text = "foobar;") Line 49 + 0x9 bytes C#
        TestAntlr-3.1.exe!TestAntlr_3._1.Program.Main(string[] args = {string[0x00000000]}) Line 30 + 0xb bytes C#
        [Native to Managed Transition]  
        [Managed to Native Transition]  
        mscorlib.dll!System.AppDomain.ExecuteAssembly(string assemblyFile, System.Security.Policy.Evidence assemblySecurity, string[] args) + 0x39 bytes    
        Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() + 0x2b bytes  
        mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) + 0x3b bytes   
        mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x81 bytes    
        mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x40 bytes

调试器的消息告诉您,源自代码之外的异常(来自NoViableAlt)将通过您在TestAntlr-3.1.exe!TimeDefLexer.mTokens()中拥有的代码而不进行处理。

措辞令人困惑,但这并不意味着例外未被捕获。调试器让你知道你拥有的代码mTokens()“需要强大,以防止通过它抛出此异常。

可以看到的是如何找到那些没有重复问题的人:

  • 转到工具/选项/调试和 关闭“启用我的代码 (仅管理)“。或选项。
  • 转到Debugger / Exceptions并关闭“User-unhandled” 公共语言运行时异常。

答案 1 :(得分:8)

无论程序集是否已被编译为发布版本,异常应该“冒泡”到调用者,因此没有理由不在调试模式下编译程序集会对其产生任何影响。

我同意Daniel建议可能在单独的线程上发生异常 - 尝试在Application.ThreadException中挂钩线程异常事件。当发生任何未处理的线程异常时,应该引发此问题。您可以调整代码: -

using System.Threading;

...

void Application_ThreadException(object sender, ThreadExceptionEventArgs e) {
  throw new ParserException(e.Exception.Message, e.Exception);
}    

 ...

 var exceptionHandler = 
    new ThreadExceptionEventHandler(Application_ThreadException);
 Application.ThreadException += exceptionHandler;
 try {
    // Execution stopped at parser.prog()
    TimeDefParser.prog_return prog_ret = parser.prog();
    return prog_ret == null ? null : prog_ret.value;
 }
 catch (Exception ex) {
    throw new ParserException(ex.Message, ex);
 }
 finally {
    Application.ThreadException -= exceptionHandler;
 }

答案 2 :(得分:5)

您使用的是.Net 1.0还是1.1?如果是这样,那么catch(Exception ex)将不会捕获非托管代码的异常。你需要使用catch {}代替。有关更多详细信息,请参阅此文章:

http://www.netfxharmonics.com/2005/10/net-20-trycatch-and-trycatchexception/

答案 3 :(得分:5)

我可以告诉你这里发生了什么......

Visual Studio正在崩溃,因为它认为异常未处理。未处理意味着什么?好吧,在Visual Studio中,工具中有一个设置...选项...调试...常规...“启用我的代码(仅管理)”。如果选中此选项并且异常传播出代码并传出到与“不是您的代码”的程序集中存在的方法调用相关联的堆栈帧(例如,Antlr),则认为是“未处理”。因此,我关闭了启用我的代码功能。但是,如果你问我,这是蹩脚的...让我们说你这样做:

ExternalClassNotMyCode c = new ExternalClassNotMyCode();
try {
    c.doSomething( () => { throw new Exception(); } );
}
catch ( Exception ex ) {}

doSomething在那里调用你的匿名函数,该函数抛出异常......

请注意,如果启用“启用我的代码”,则根据Visual Studio,这是一个“未处理的异常”。另请注意,在调试模式下它会像断点一样停止,但在非调试或生产环境中,代码完全有效并且按预期工作。此外,如果你只是在调试器中“继续”,应用程序继续它的快乐方式(它不会停止线程)。它被认为是“未处理的”,因为异常通过不在代码中的堆栈帧传播(即在外部库中)。如果你问我,这很糟糕。请更改此默认行为Microsoft。这是使用Exceptions控制程序逻辑的完全有效的情况。有时,您不能将第三方库更改为以任何其他方式运行,这是完成许多任务的非常有用的方法。

以MyBatis为例,您可以使用此技术停止处理通过调用SqlMapper.QueryWithRowDelegate收集的记录。

答案 4 :(得分:3)

是否有可能在另一个线程中抛出异常?显然你的调用代码是单线程的,但是你正在使用的库可能正在进行一些多线程操作。

答案 5 :(得分:3)

我和@Shaun Austin在一起 - 尝试使用完全限定名称

包装该尝试
catch (System.Exception)

并查看是否有帮助.ANTLR文档是否说出应该抛出异常?

答案 6 :(得分:2)

我下载了样本VS2008项目,我也有点难过。然而,我能够通过例外,虽然可能不会以适合你的方式运行。但这就是我发现的:

mailing list post讨论了您遇到的相同问题。

从那里,我在主program.cs文件中添加了几个虚拟类:

class MyNoViableAltException : Exception
{
    public MyNoViableAltException()
    {
    }
    public MyNoViableAltException(string grammarDecisionDescription, int decisionNumber, int stateNumber, Antlr.Runtime.IIntStream input)
    {
    }
}
class MyEarlyExitException : Exception
{
    public MyEarlyExitException()
    {
    }

    public MyEarlyExitException(int decisionNumber, Antlr.Runtime.IIntStream input)
    {
    }
}

然后将使用行添加到TimeDefParser.cs和TimeDefLexer.cs中:

using NoViableAltException = MyNoViableAltException;
using EarlyExitException = NoViableAltException; 

由于异常会冒泡进入伪异常类并且可以在那里处理,但是在TimeDefLexer.cs中的mTokens方法中仍然会抛出异常。将该类包含在该类中的try catch中包含异常:

            try
            {
                alt4 = dfa4.Predict(input);
            }
            catch
            {
            }

我真的不明白为什么将它包装在内部方法中而不是在处理错误的地方处理错误,如果线程没有发挥作用,但无论如何希望这会指向一个比我更聪明的人在正确的方向

答案 7 :(得分:2)

Steve Steiner是正确的,异常来自antlr库,通过mTokens()方法并被antlr库捕获。问题是这个方法是由antlr自动生成的。因此,在生成解析器/词法分析器类时,将覆盖在mTokens()中处理异常的任何更改。

默认情况下,antlr会记录错误并尝试恢复解析。您可以覆盖它,以便每当遇到错误时,parser.prog()都会抛出异常。从您的示例代码中我认为这是您期望的行为。

将此代码添加到您的语法(.g)文件中。您还需要在调试菜单中关闭“启用我的代码”。

@members {

    public override Object RecoverFromMismatchedSet(IIntStream input,RecognitionException e,    BitSet follow)  
    {
        throw e;
    }
}

@rulecatch {
    catch (RecognitionException e) 
    {
        throw e;
    }
}

这是我在“最终的ANTLR参考”一书的“退出第一个错误的识别器”一章中给出的C#版本的尝试。

希望这就是你要找的东西。

答案 8 :(得分:2)

嗯,我不明白这个问题。我下载并尝试了您的示例解决方案文件。

在TimeDefLexer.cs第852行引发了一个异常,它随后由Program.cs中的catch块处理,只是说 Handled exception

如果我取消注释它上面的catch块,它将进入该块。

这里似乎有什么问题?

正如Kibbee所说,Visual Studio将停止异常,但是如果你要求它继续,那么异常将被你的代码捕获。

答案 9 :(得分:2)

  

对我来说,catch(Exception)子句应该捕获任何异常。有什么理由不这样做吗?

我能想到的唯一可能性是,在您面前以其他方式处理它并以一种似乎未被捕获的异常(例如退出流程)的方式处理它。

  

我的try / catch块不会捕获它,而是将执行作为未处理的异常停止。

您需要找到导致退出流程的原因。它可能不是未处理的异常。 您可以尝试使用本机调试器,并在“{,, kernel32.dll} ExitProcess”上设置断点。然后使用SOS确定哪些托管代码正在调用退出流程。

答案 10 :(得分:2)

就个人而言,我根本不相信线程理论。

我之前见过这一次,我正在使用一个库,它也定义了Exception和使用我的意思是实际的Catch指的是一个不同的“异常”类型(如果它已经完全限定了它是Company.Lib.Exception,但它不是因为使用)所以当它捕获正在抛出的正常异常(如果我没记错的某种参数异常)它只是不会捕获它因为类型没有'匹配。

总而言之,在该类中使用的另一个命名空间中是否有另一个Exception类型?

编辑:检查这个的一个快速方法是确保在你的catch子句中你完全限定Exception类型为“System.Exception”并给它一个旋转!

EDIT2:好的,我已经尝试过代码并承认失败了。如果没有人提出解决方案的话,我必须在早上再看看它。

答案 11 :(得分:2)

我下载了您的代码,一切都按预期工作。

Visual Studio调试器正确拦截所有异常。 Catch块按预期工作。

我正在运行Windows 2003服务器SP2,VS2008 Team Suite(9.0.30729.1 SP)

我尝试为.NET 2.0,3.0&编译项目。 3.5

@Steve Steiner,您提到的调试器选项与此行为无关。

我尝试使用这些选项而没有可见效果 - 捕获块设法拦截所有异常。

答案 12 :(得分:1)

哦,并参考Kibbee说的话;如果在VS中选择Debug | Exceptions并且只需单击'thrown'列中的所有框,它应该选择所有 up AFAIK作为'第一次机会异常',即VS将指示异常何时< em> about 由其他所有内容处理并打破相关代码。这应该有助于调试。

答案 13 :(得分:1)

您是否尝试在catch子句中打印(Console.WriteLine())异常,而不使用visual studio并在控制台上运行您的应用程序?

答案 14 :(得分:1)

我相信Steve Steiner是对的。在研究Steve的建议时,我遇到了this thread在工具|选项|调试器|常规中讨论“启用我的代码”选项。当非用户代码抛出或处理异常时,建议调试器在某些条件下中断。我不确定为什么这甚至是重要的,或者为什么调试器明确地说异常在它真的没有处理时。

我可以通过禁用“启用我的代码”选项来消除错误中断。这也会通过删除“用户处理”列来更改“调试|例外”对话框,因为它不再适用。或者,您可以取消选中CLR的“用户处理”框并获得相同的结果。

大大感谢大家的帮助!

答案 15 :(得分:1)

  

我用Reflector追踪外部组件,没有发现任何线程的证据。

您找不到任何线程并不意味着没有线程

.NET有一个'线程池',它是一组“备用”线程,大多数空闲。某些方法会导致事物在其中一个线程池线程中运行,因此它们不会阻止您的主应用程序。

明显的例子是像ThreadPool.QueueUserWorkItem这样的东西,但是还有很多其他东西也可以在线程池中运行看起来不那么明显的东西,比如Delegate.BeginInvoke

真的,你需要do what kibbee suggests

答案 16 :(得分:1)

最好的选择听起来像设置Visual Studio来打破所有未处理的异常(Debug - &gt; Exceptions对话框,选中“公共语言运行时异常”框,也可能选中其他异常框)。然后在调试模式下运行程序。当ANTLR解析器代码抛出异常时,它应该被Visual Studio捕获并允许您查看它发生的位置,异常类型等。

根据描述,catch块似乎是正确的,因此可能会发生以下几种情况之一:

  1. 解析器实际上没有抛出异常
  2. 解析器最终会抛出一些不是从System.Exception派生的东西
  3. 在另一个未处理的线程上抛出异常
  4. 听起来你可能已经排除了问题#3。

答案 17 :(得分:1)

一旦发生任何异常,您就可以将VS.Net设置为中断。只需在调试模式下运行您的项目,它将在抛出异常后立即停止。然后你应该更好地了解为什么它没有被抓住。

此外,您可以将一些代码放入catch all unhandled exceptions。阅读链接了解更多信息,但基本知识是这两行。

Application.ThreadException += new ThreadExceptionEventHandler(ThreadExceptionHandler);

 // Catch all unhandled exceptions in all threads.
 AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionHandler);

答案 18 :(得分:0)

如果你正在使用com对象你的项目并尝试catch块而不是捕获异常,那么当异常跨越AppDomain或托管/本地边界(仅管理)选项时,需要禁用Tools / Debugging / Break。

答案 19 :(得分:0)

哇,到目前为止,报告中有2个正常工作,1个人遇到了我报告的问题。有哪些版本的Windows,使用的Visual Studio和带有内部版本号的.NET框架?

我正在运行XP SP2,VS 2008 Team Suite(9.0.30729.1 SP),C#2008(91899-270-92311015-60837)和.NET 3.5 SP1。

答案 20 :(得分:0)

@spoulson,

如果您可以复制它,可以将它发布到某个地方吗?您可以尝试的一个途径是使用SOS扩展来使用WinDBG来运行应用程序并捕获未处理的异常。它将在第一次机会异常(在运行时尝试查找处理程序之前)中断,并且您可以在此处看到它来自何处以及什么线程。

如果您以前没有使用过WinDBG,那可能会有点压倒性的,但这是一个很好的教程:

http://blogs.msdn.com/johan/archive/2007/11/13/getting-started-with-windbg-part-i.aspx

启动WinDBG后,您可以转到Debug-&gt;事件过滤器来切换未处理的异常。

答案 21 :(得分:0)

我不确定我是否不清楚,但如果是这样,我看到调试器停止执行NoViableAltException类型的“Unhandled Exception”。最初,我对这个Debug-&gt; Exceptions菜单项一无所知,因为MS希望您在VS安装时能够在不知道它们如何不同时提交到配置文件。显然,I was not on the C# dev profile and was missing this option。在最终调试所有抛出的CLR异常之后,遗憾的是我无法发现导致此未处理异常问题的原因的任何新行为。抛出的所有异常都是预期的,并且应该在try / catch块中处理。

我查看了外部装配,没有多线程的证据。通过这种方式,我的意思是System.Threading没有引用,也没有使用任何代理。我熟悉它构成了一个实例化线程。我通过在未处理的异常时观察线程工具箱来验证这一点,以查看只有一个正在运行的线程。

我与ANTLR人员有一个公开的问题,所以也许他们以前能够解决这个问题。我已经能够在VS 2008和VS 2005下使用.NET 2.0和3.5在一个简单的控制台应用程序项目中复制它。

这只是一个痛点,因为它强制我的代码只能使用已知的有效解析器输入。如果根据用户输入抛出未处理的异常,则使用IsValid()方法会有风险。当有更多人了解这个问题时,我会及时更新这个问题。

答案 22 :(得分:0)

我同意Daniel Augerkronoz这类似于与线程有关的异常。除此之外,这是我的其他问题:

  1. 完整的错误消息说什么?它有什么例外?
  2. 根据您在此处提供的堆栈跟踪,您的代码在TimeDefLexer.mTokens()中抛出的异常不是吗?

答案 23 :(得分:0)

我不明白...你的catch块只会抛出一个新的异常(使用相同的消息)。这意味着你的陈述:

  

问题在于,在某些情况下(并非所有)我的try / catch块都不会捕获它,而是将执行作为未处理的异常停止。

正是预期的发生。

答案 24 :(得分:0)

  

“此外,您可以将一些代码放入   捕获所有未处理的异常。读   更多信息的链接,但基础知识   是这两行。“

这是错误的。这用于捕获.NET 1.0 / 1.1中的所有未处理的异常,但它是一个错误,它不应该和它在.NET 2.0中修复。

AppDomain.CurrentDomain.UnhandledException 

仅用作最后机会记录沙龙,因此您可以在程序退出之前记录异常。从2.0开始它不会捕获异常(尽管在.NET 2.0中至少有一个配置值,你可以修改它以使它像1.1一样运行但不建议使用它。)。

值得注意的是,无法捕获的异常很少,例如StackOverflowException和OutOfMemoryException。否则,正如其他人所建议的那样,它可能是某个后台线程中的异常。此外,我很确定你也无法捕获一些/所有非托管/本机异常。