Почему исключение .NET не перехватывается блоком try / catch? - PullRequest
42 голосов
/ 30 августа 2008

Я работаю над проектом с использованием библиотеки синтаксического анализатора ANTLR для C #. Я построил грамматику для разбора текста, и она работает хорошо. Однако, когда синтаксический анализатор обнаруживает недопустимый или неожиданный токен, он выдает одно из многих исключений. Проблема в том, что в некоторых случаях (не во всех) мой блок 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. Проблема обработки исключений, с которой я столкнулся, никогда не достигает этой строки кода. Фактически, я прокомментировал часть «throw new ParserException» и все еще получил тот же результат.

Еще одна вещь, я изменил исходный рассматриваемый блок try / catch, чтобы вместо этого перехватывать NoViableAltException, устраняя любую путаницу наследования. Я все еще получил тот же результат.

Кто-то однажды предположил, что иногда VS чрезмерно активен при перехвате обработанных исключений в режиме отладки, но эта проблема также возникает в режиме выпуска.

Чувак, я все еще в тупике! Я не упоминал об этом раньше, но я использую VS 2008, и весь мой код 3.5. Внешняя сборка 2.0. Кроме того, некоторые из моего кода являются подклассами класса в сборке 2.0. Может ли несовпадение версий вызвать эту проблему?

Обновление 2: Мне удалось устранить конфликт версий .NET, портировав соответствующие части моего кода .NET 3.5 в проект .NET 2.0 и повторив тот же сценарий. Мне удалось реплицировать одно и то же необработанное исключение при постоянной работе в .NET 2.0.

Я узнал, что ANTLR недавно выпустил 3.1. Итак, я обновился с 3.0.1 и повторил попытку. Оказывается, сгенерированный код немного переработан, но в моих тестовых примерах происходит то же необработанное исключение.

Обновление 3: Я повторил этот сценарий в упрощенном проекте VS 2008 . Не стесняйтесь загружать и проверять проект для себя. Я применил все замечательные предложения, но пока не смог преодолеть это препятствие.

Если вы можете найти обходной путь, пожалуйста, поделитесь своими выводами. Еще раз спасибо!


Спасибо, но VS 2008 автоматически разбивается на необработанные исключения. Кроме того, у меня нет диалога Debug-> Exceptions. Создаваемое исключение NoViableAltException предназначено для перехвата кода пользователя. Поскольку он не перехвачен должным образом, выполнение программы неожиданно останавливается как необработанное исключение.

Исключение выдается из Exception, и многопоточность с ANTLR не выполняется.

Ответы [ 25 ]

30 голосов
/ 03 сентября 2008

Мне кажется, я понимаю проблему. Обнаружено исключение, проблема заключается в путанице в поведении отладчика и различиях в настройках отладчика для каждого человека, пытающегося его воспроизвести.

В третьем случае из вашего репро, я полагаю, вы получаете следующее сообщение: «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] 

Если вы щелкнете правой кнопкой мыши в окне callstack и запустите команду 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 () "должен быть устойчивым к этому исключению.

С чем поиграть, чтобы посмотреть, как это выглядит для тех, кто не повторил проблему:

  • Перейдите в Инструменты / Параметры / Отладка и отключить "Включить только мой код (Управляется только) ". Или опция.
  • Перейдите в «Отладчик / Исключения» и отключите «Необработанный пользователь» для Общеязыковые исключения во время выполнения.
8 голосов
/ 30 августа 2008

Независимо от того, была ли сборка скомпилирована как сборка релиза, исключение должно определенно «пузыриться» до вызывающей стороны, нет причины, по которой сборка, не скомпилированная в режиме отладки, должна на это повлиять.

Я согласен с предположением Дэниела о том, что, возможно, исключение происходит в отдельном потоке - попробуйте перехватить событие исключения потока в 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;
 }
5 голосов
/ 30 августа 2008

Вы используете .Net 1.0 или 1.1? Если это так, то catch (Exception ex) не будет ловить исключения из неуправляемого кода. Вам нужно будет использовать catch {}. См. Эту статью для получения дополнительной информации:

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

5 голосов
/ 20 января 2012

Я могу рассказать вам, что здесь происходит ...

Visual Studio ломается, потому что считает исключение необработанным. Что значит необработанный? Ну, в Visual Studio есть настройка в меню Инструменты ... Параметры ... Отладка ... Общие ... "Включить только мой код (только управляемый)". Если это проверено, и если исключение распространяется из вашего кода и выходит в кадр стека, связанный с вызовом метода, который существует в сборке, которая «НЕ ВАШ КОД» (например, Antlr), это считается «необработанным». По этой причине я отключаю эту функцию. Но, если вы спросите меня, это неубедительно ... допустим, вы делаете это:

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

doSomething вызывает вашу анонимную функцию, и эта функция вызывает исключение ...

Обратите внимание, что это «необработанное исключение» в соответствии с Visual Studio, если включено «Включить только мой код». Кроме того, обратите внимание, что он останавливается, как если бы он был точкой останова в режиме отладки, но в среде без отладки или в рабочей среде код является абсолютно корректным и работает, как и ожидалось. Кроме того, если вы просто «продолжаете» в отладчике, приложение работает очень весело (оно не останавливает поток). Это считается «необработанным», потому что исключение распространяется через кадр стека, который НЕ находится в вашем коде (т.е. во внешней библиотеке). Если вы спросите меня, это паршиво. Пожалуйста, измените это поведение по умолчанию Microsoft. Это вполне допустимый случай использования исключений для управления логикой программы. Иногда вы не можете изменить стороннюю библиотеку на другое поведение, и это очень полезный способ решения многих задач.

Возьмем, к примеру, MyBatis, вы можете использовать эту технику, чтобы остановить обработку записей, которые собираются при вызове SqlMapper.QueryWithRowDelegate.

3 голосов
/ 02 сентября 2008

Я с @Shaun Austin - попробуйте завершить попытку с полным именем

catch (System.Exception)

и посмотрите, поможет ли это. В документе ANTLR говорится, какие исключения следует выдавать?

3 голосов
/ 30 августа 2008

Возможно ли, что исключение выдается в другом потоке? Очевидно, ваш вызывающий код является однопоточным, но, возможно, используемая вами библиотека выполняет некоторые многопоточные операции под прикрытием.

2 голосов
/ 10 марта 2009

Стив Штейнер прав, что исключение происходит из библиотеки 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;
    }
}

Это моя попытка версии на C # примера, приведенного в главе «Выход из распознавателя при первой ошибке» книги «Полное руководство по ANTLR».

Надеюсь, это то, что вы искали.

2 голосов
/ 03 сентября 2008

Я скачал твой код и все работает как положено.

Отладчик Visual Studio правильно перехватывает все исключения. Поймать блоки работают как положено.

Я использую Windows 2003 server SP2, VS2008 Team Suite (9.0.30729.1 SP)

Я пытался скомпилировать ваш проект для .NET 2.0, 3.0 и 3.5

@ Steve Steiner, упомянутые вами опции отладчика не имеют ничего общего с этим поведением.

Я попытался поиграть с этими опциями без видимых эффектов - блоки перехвата смогли перехватить все исключения.

2 голосов
/ 03 сентября 2008

Хм, я не понимаю проблемы. Я скачал и попробовал ваш пример файла решения.

Исключение выдается в TimeDefLexer.cs, строка 852, которая впоследствии обрабатывается блоком catch в Program.cs, который просто говорит Обработанное исключение .

Если я раскомментирую блок catch над ним, он войдет в этот блок.

В чем здесь проблема?

Как сказал Кибби, Visual Studio остановится на исключениях, но если вы попросите его продолжить, исключение будет перехвачено вашим кодом.

2 голосов
/ 03 сентября 2008

Я скачал пример проекта VS2008, и здесь я тоже немного озадачен. Однако я смог преодолеть исключения, хотя, вероятно, не так, чтобы это сработало. Но вот что я нашел:

В этом сообщении рассылки обсуждалось то, что похоже на ту же проблему, с которой вы столкнулись.

Оттуда я добавил пару фиктивных классов в основной файл 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; 

При этом исключения будут пузыриться в ложные классы исключений и могут обрабатываться там, но в методе mTokens в TimeDefLexer.cs по-прежнему возникает исключение. Обтекание, что при попытке catch в этом классе произошло исключение:

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

Я действительно не понимаю, почему оборачивать его во внутренний метод, а не туда, откуда он вызывается, обрабатывать ошибку, если многопоточность не в игре, но в любом случае, надеюсь, это укажет кого-то умнее меня в правильном направлении .

...