English 中文(简体)
为什么try/catch块没有捕获.NET异常?
原标题:
  • 时间:2008-08-30 14:57:13
  •  标签:

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

对我来说,问题是除了在我的完整代码中,我不能在其他任何地方复制这个问题。调用堆栈显示异常肯定发生在我的try/catch(exception)块中。我唯一能想到的是,在我的代码和抛出异常的代码之间发生了一些ANTLR程序集调用,而这个库没有启用调试,所以我无法进行调试。我想知道不可调试的程序集是否会抑制异常冒泡?调用堆栈如下所示;外部程序集调用位于Antlr中。运行时:

    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->;例外当lexer无法理解流中的下一个令牌时,会引发此异常;换句话说,无效输入。这个异常应该会发生,但是它应该被我的try/catch块捕获。

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

还有一件事,我修改了有问题的原始try/catch块,改为捕获NoViableAltException,消除了任何继承混淆。我仍然得到了同样的结果。

有人曾经提出,有时VS在调试模式下捕捉已处理的异常时过于活跃,但这个问题也会在发布模式下发生。

伙计,我还是被难住了!我以前没有提到过,但我正在运行VS2008,我所有的代码都是3.5。外部组件为2.0。此外,我的一些代码在2.0程序集中对一个类进行了子类化。版本不匹配是否会导致此问题?

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

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

Update 3: I ve replicated this scenario in a simplified VS 2008 project. Feel free to download and inspect the project for yourself. I ve applied all the great suggestions, but have not been able to overcome this obstacle yet.

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


谢谢,但VS2008会自动中断未处理的异常。此外,我没有调试->;异常对话框。抛出的NoViableAltException是完全有意的,并且被设计为由用户代码捕获。由于未按预期捕获,因此程序执行会作为未处理的异常意外中止。

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

最佳回答

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

在您的repo的第三种情况下,我相信您会收到以下消息:“用户代码未处理NoViableAltException”和一个看起来像这样的调用堆栈:

         [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] 

如果右键单击调用堆栈窗口并运行turn-on-show外部代码,您会看到以下内容:

        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()“需要对通过它抛出的异常具有鲁棒性。

可以玩的东西,看看这对那些没有解决问题的人来说是什么样子:

  • Go to Tools/Options/Debugging and turn off "Enable Just My code (Managed only)". or option.
  • Go to Debugger/Exceptions and turn off "User-unhandled" for Common-Language Runtime Exceptions.
问题回答

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

Visual Studio正在中断,因为它认为该异常未得到处理。未处理是什么意思?好吧,在Visual Studio中,“工具”中有一个设置。。。选项。。。正在调试。。。全体的“仅启用我的代码(仅限托管)”。如果选中此项,并且异常从代码中传播到与“NOT your code”(例如,Antlr)程序集中存在的方法调用相关联的堆栈帧,则被视为“未处理”。出于这个原因,我关闭了“仅启用我的代码”功能。但是,如果你问我,这太差劲了。。。假设您这样做:

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

doSomething在那里调用您的匿名函数,而该函数抛出异常。。。

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

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

无论程序集是否已作为发布版本编译,异常肯定会出现在调用方身上,没有理由不以调试模式编译程序集会对此产生任何影响。

我同意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/

我和@Shaun Austin在一起-试着用完全限定的名字来结束尝试

catch (System.Exception)

看看这是否有帮助。ANTLR文档是否说明了应该抛出哪些异常?

异常是否可能是在另一个线程中抛出的?很明显,您的调用代码是单线程的,但您正在使用的库可能在幕后执行一些多线程操作。

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

我能想到的唯一可能性是,其他人在你之前捕获了它,并以一种看似未捕获的异常的方式处理它(例如退出进程)。

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

You need to find what is causing the exit process. It might be something other than an unhandled exception. You might try using the native debugger with a breakpoint set on "{,,kernel32.dll}ExitProcess". Then use SOS to determine what managed code is calling exit process.

就我个人而言,我一点也不相信线程理论。

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

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

编辑:检查这一点的一个快速方法是确保在catch子句中,您将Exception类型完全限定为“System.Exception”,并对其进行测试!

编辑2:好的,我已经试过了代码,现在承认失败了。如果没有人拿出解决方案,我将不得不在早上再看一眼。

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

在TimeDefLexer.cs的第852行中抛出一个异常,随后由Program.cs中的catch块处理,该块只表示已处理异常

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

这里好像出了什么问题?

正如Kibbee所说,VisualStudio会在出现异常时停止,但如果您要求它继续,则异常会被您的代码捕获。

我下载了VS2008项目的示例,在这里也有点困惑。然而,我能够通过例外情况,尽管可能不会以对你很好的方式工作。但我发现:

邮件列表帖子讨论了您正在经历的相同问题。

从那里,我在主程序.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
            {
            }

我真的不明白为什么要把它包装在内部方法中,而不是从哪里调用它。如果线程不起作用,就处理错误,但无论如何,希望这能给比我更聪明的人指明正确的方向。

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

Visual Studio调试器正确地截获所有异常。挡块按预期工作。

我正在运行Windows 2003服务器SP2、VS2008团队套件(9.0.30729.1 SP)

我试着为.NET 2.0、3.0&;3.5

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

我试着在没有可见效果的情况下使用这些选项——catch块成功地拦截了所有异常。

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

默认情况下,antlr将记录错误并尝试恢复解析。您可以重写它,以便解析器.prog()在遇到错误时抛出异常。从您的示例代码来看,我认为这是您所期望的行为。

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

@members {

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

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

这是我对“最终ANTLR参考”一书的“第一次出错时退出识别器”一章中给出的例子的C#版本的尝试。

希望这就是你想要的。

您可以将VS.Net设置为在出现任何异常时立即中断。只需在调试模式下运行您的项目,一旦抛出异常,它就会停止。那么你应该更清楚为什么它没有被抓住。

此外,您还可以放入一些代码来捕获所有未处理的异常。

Application.ThreadException += new ThreadExceptionEventHandler(ThreadExceptionHandler);

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

哦,关于Kibbee所说的话;如果您在VS中选择Debug|Exceptions,然后只点击抛出列中的所有框,它应该选择AFAIK上的everything作为第一次机会异常,即VS将指示异常何时是关于,以便由其他一切处理,并中断相关代码。这应该有助于调试。

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

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

  1. the parser is not actually throwing an exception
  2. the parser is ultimately throwing something that isn t deriving from System.Exception
  3. there is an exception being thrown on another thread that isn t being handled

听起来你可能已经排除了第三个问题。

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

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

.NET有一个线程池,它是一组空闲的线程,这些线程大部分处于空闲状态。某些方法会导致某些事情在某个线程池线程中运行,这样它们就不会阻塞您的主应用程序。

公然的例子是ThreadPool.QueueUserWorkItem,但还有很多其他东西也可以在线程池中运行,这些东西看起来并不那么明显,比如委派。开始投票

真的,你需要按照kibbee的建议去做

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

我相信Steve Steiner是正确的。在研究Steve的建议时,我遇到了this thread谈论“工具”|“选项”|“调试器”|“常规”中的“仅启用我的代码”选项。有人建议,当非用户代码引发或处理异常时,调试器会在某些情况下中断。我不太清楚为什么这很重要,也不清楚为什么调试器明确表示异常未得到处理。

我能够通过禁用“仅启用我的代码”选项来消除假中断。这还通过删除不再适用的“用户处理”列来更改“调试|异常”对话框。或者,您只需取消选中CLR的“用户处理”框,即可获得相同的结果。

非常感谢大家的帮助!

"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中得到了修复。

AppDomain.CurrentDomain.UnhandledException 

仅用作最后一次记录沙龙,以便您可以在程序退出之前记录异常。从2.0开始,它不会捕捉到异常(尽管在.NET 2.0中,至少有一个配置值可以修改,使其看起来像1.1,但不建议使用它。)。

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

我不明白…你的catch块只是抛出了一个新的异常(带有相同的消息)。这意味着你的声明:

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

这正是预期发生的

我同意Daniel Augerkronoz这闻起来像是一个与线程有关的异常。除此之外,我还有其他问题:

  1. What does the complete error message say? What kind of exception is it?
  2. Based on the stack trace you ve provided here, isn t the exception thrown by you code in TimeDefLexer.mTokens()?

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

我查看了外部程序集,没有证据表明存在多线程。我的意思是,不存在对System.Threading的引用,也没有使用任何委托。我对实例化线程的构成很熟悉。我通过在发生未处理的异常时观察线程工具箱来验证这一点,以查看只有一个正在运行的线程。

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

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

@斯波森,

如果你能复制它,你能把它发布到某个地方吗?你可以尝试的一种方法是使用带有SOS扩展的WinDBG来运行应用程序并捕获未处理的异常。它会在第一次出现异常时中断(在运行时试图找到处理程序之前),此时您可以看到它来自哪里,以及是哪个线程。

如果你以前没有使用过WinDBG,它可能会有点让人不知所措,但这里有一个很好的教程:

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

启动WinDBG后,您可以通过转到Debug->;来切换未处理异常的中断;事件筛选器。

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

如果您在项目中使用com对象,并尝试捕获块而不捕获异常,则需要禁用“工具/调试/中断”(当异常跨越AppDomain或托管/本机边界时)选项(仅限托管)。





相关问题