关于c#:为什么.NET异常没有被try / catch块捕获?

关于c#:为什么.NET异常没有被try / catch块捕获?

Why is .NET exception not caught by try/catch block?

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

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

1
2
3
4
5
6
    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()中最底部调用的代码片段如下所示:

1
2
3
4
5
6
7
8
     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 - > Exception。当词法分析器无法理解流中的下一个标记时,抛出此异常;换句话说,输入无效。这个例外是支持发生的,但它应该被我的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:
我在简化的VS 2008项目中复制了这个场景。您可以自己下载并检查项目。我已经应用了所有伟大的建议,但还未能克服这个障碍。

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

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

抛出的异常源自Exception,并且ANTLR没有进行多线程处理。


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

在您的repro的第三种情况下,我相信您收到以下消息:"NoViableAltException未被用户代码处理"和一个如下所示的callstack:

1
2
3
4
5
6
7
         [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,您会看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        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"
    公共语言运行时异常。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
 }

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

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

1
2
3
4
5
ExternalClassNotMyCode c = new ExternalClassNotMyCode();
try {
    c.doSomething( () => { throw new Exception(); } );
}
catch ( Exception ex ) {}

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

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

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


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

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


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

1
catch (System.Exception)

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


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


To me, a catch (Exception) clause should've captured any exception whatsoever. Is there any reason why it wouldn't?

我能想到的唯一可能性就是其他东西在你面前捕捉它并以一种看似未被捕获的异常(例如退出过程)的方式处理它。

my try/catch block won't catch it and instead stops execution as an unhandled exception.

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


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

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

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

这里似乎有什么问题?

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


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

这封邮件列表帖子讨论了您遇到的相同问题。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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中:

1
2
using NoViableAltException = MyNoViableAltException;
using EarlyExitException = NoViableAltException;

有了这个,异常会冒泡进入伪异常类并且可以在那里处理,但是在TimeDefLexer.cs中的mTokens方法中仍然会抛出异常。在该类的try catch中包装该异常会捕获异常:

1
2
3
4
5
6
7
            try
            {
                alt4 = dfa4.Predict(input);
            }
            catch
            {
            }

我真的不明白为什么将它包装在内部方法中,而不是在处理错误的情况下,如果线程没有在游戏中处理错误,但无论如何希望这将指向比我更聪明的人在正确的方向。


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

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

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

我试图为.NET 2.0,3.0和3.5编译你??的项目

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

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


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@members {

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

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

这是我尝试在"确定的ANTLR参考"一书的"退出第一个错误的识别器"章节中给出的C#版本的例子。

希望这是你想要的。


就个人而言,我完全不相信线程理论。

有一次我以前见过这个,我正在使用一个库,它也定义了Exception和使用我的意思是实际的Catch指的是一个不同的"例外"类型(如果它完全合格,那就是公司。 Lib.Exception,但它不是因为使用)所以当它捕获正在抛出的正常异常(如果我没记错的某种参数异常)它只是不会捕获它,因为类型不匹配。

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

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

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


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


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

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

1
2
3
4
Application.ThreadException += new ThreadExceptionEventHandler(ThreadExceptionHandler);

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


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


我相信Steve Steiner是对的。在研究Steve的建议时,我遇到了这个线程,讨论了Tools | Options | Debugger | General中的"Enable Just My Code"选项。当非用户代码抛出或处理异常时,建议调试器在某些条件下中断。我不确定为什么这甚至是重要的,或者为什么调试器明确地说异常在它真的没有处理时。

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

非常感谢大家的帮助!


I traced through the external assembly with Reflector and found no evidence of threading whatsoever.

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

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

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

真的,你需要做kibbee建议的事情。


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

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

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


    "Also, you can put some code in to
    catch all unhandled exceptions. Read
    the link for more info, but the basics
    are these two lines."

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

    1
    AppDomain.CurrentDomain.UnhandledException

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

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


    我没有得到它...你的catch块只是抛出一个新的异常(使用相同的消息)。意思是你的陈述:

    The problem is that in some cases (not all) that my try/catch block won't catch it and instead stops execution as an unhandled exception.

    正是预期会发生的事情。


    我同意Daniel Auger和kronoz的观点,这种异味与线程有关。除此之外,这是我的其他问题:

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

  • @spoulson,

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

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

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

    一旦启动WinDBG,您可以通过转到Debug-> Event Filters来切换未处理异常的中断。


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


    哇,到目前为止的报告中,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。


    我不确定我是否不清楚,但如果是这样,我看到调试器停止执行NoViableAltException类型的"Unhandled Exception"。最初,我对这个Debug-> Exceptions菜单项一无所知,因为MS希望你在VS安装时,在你不知道它们有什么不同的情况下提交一个配置文件。显然,我没有在C#dev配置文件中,并且缺少此选项。在最终调试所有抛出的CLR异常之后,遗憾的是我无法发现导致此未处理异常问题的原因的任何新行为。抛出的所有异常都是预期的,并且应该在try / catch块中处理。

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

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

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


    推荐阅读