我正在使用针对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 - > 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:
[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并关闭Common-Language Runtime Exceptions的"User-unhandled".
无论程序集是否已被编译为发布版本,异常应该"冒泡"到调用者,因此没有理由不在调试模式下编译程序集会对其产生任何影响.
我同意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; }
您使用的是.Net 1.0或1.1吗?如果是这样,那么catch(Exception ex)将不会捕获非托管代码的异常.你需要使用catch {}代替.有关详细信息,请参阅此文章:
http://www.netfxharmonics.com/2005/10/net-20-trycatch-and-trycatchexception/
我可以告诉你这里发生了什么......
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收集的记录.