2016-06-05 1 views
5

Я создаю переднюю нить и фоновый поток, бросая в них исключение.Почему необработанное исключение в этом фоновом потоке не завершает мой процесс?

using System; 
using System.Threading; 

namespace OriginalCallStackIsLostOnRethrow 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      try 
      { 
       A2(); 

       // Uncomment this to see how the unhandled 
       // exception in the foreground thread causes 
       // the program to terminate 
       // An exception in this foreground thread 
       // *does* terminate the program 
       // var t = new Thread(() => { 
       //  throw new DivideByZeroException(); 
       // }); 

       // t.Start(); 
      } 
      catch (Exception ex) 
      { 
       // I am not expecting anything from the 
       // threads to come here, which is fine 
       Console.WriteLine(ex); 
      } 
      finally 
      { 
       Console.WriteLine("Press any key to exit..."); 
       Console.ReadKey(); 
      } 
     } 

     static void A2() { B2(); } 
     static void B2() { C2(); } 
     static void C2() { D2(); } 
     static void D2() 
     { 
      Action action =() => 
      { 
       Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}. Exception will occur while running D2"); 
       throw new DivideByZeroException(); 
       Console.WriteLine("Do we get here? Obviously not!"); 
      }; 
      action.BeginInvoke(ar => Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}"), null); 
     } 
    } 
} 

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

Эта программа, таким образом, получается следующий результат:

Press any key to exit... 
D2 called on worker #6. Exception will occur while running D2 
D2 completed on worker thread #6 

Это бросает вызов моего понимания об обработке исключений в потоках. Мое понимание заключалось в том, что независимо от характера потока необработанное исключение, начиная с версии 2.0 каркаса, приведет к завершению процесса.

Вот цитата из the documentation по этой теме:

статус или задний план нити не влияет на исход необработанного исключения в потоке. В .NET Framework версии 2.0 необработанное исключение либо на переднем плане, либо на фоне потоков приводит к прекращению приложения. См. Исключения в Управляемые потоки.

Более того, на странице под названием Exceptions in Managed Threads состояния:

Начиная с .NET Framework версии 2.0, общий язык среда позволяет большинству необработанных исключений в резьб для продолжения естественно. В большинстве случаев это означает, что необработанное исключение заставляет приложение завершить работу.

Это существенное изменение в версиях .NET Framework версии 1.0 и 1.1, которые обеспечивают обратную блокировку для многих необработанных исключений - например, необработанных исключений в потоках пулов потоков. См. Изменение с Предыдущие версии далее в этой теме.

ЕЩЕ ИНТЕРЕСНЫЕ НАБЛЮДЕНИЯ

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

using System; 
using System.Threading; 

namespace OriginalCallStackIsLostOnRethrow 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      try 
      { 
       // A2(); 
       A3(); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex); 
      } 
      finally 
      { 
       Console.WriteLine("Press any key to exit..."); 
       Console.ReadKey(); 
      } 
     } 

     static void A2() { B2(); } 
     static void B2() { C2(); } 
     static void C2() { D2(); } 
     static void D2() 
     { 
      Action action =() => 
      { 
       try 
       { 
        Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}. Exception will occur while running D2"); 
        throw new DivideByZeroException(); 
        // Console.WriteLine("Do we get here? Obviously not!"); 
       } 
       catch(Exception ex) 
       { 
        Console.WriteLine(ex); 
       } 
      }; 
      action.BeginInvoke(ar => Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}"), null); 
     } 

     static void A3() { B3(); } 
     static void B3() { C3(); } 
     static void C3() { D3(); } 
     static void D3() 
     { 
      Action action =() => { Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}."); }; 
      action.BeginInvoke(ar => 
      { 
       Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}. Oh, but wait! Exception!"); 

       // This one on the completion callback does terminate the program 
       throw new DivideByZeroException(); 
      }, null); 
     } 
    } 
} 

ОЧЕРЕДНОЙ РАЗ ИНТЕРЕСНО НАБЛЮДЕНИЯ

Кроме того, еще более интересно, если вы обработать исключение в действие, которое вы хотите выполнить с помощью APM, в catch блоке (установить точку останова в улове блок в D2()), появляется Exception, который не имеет трассировки стека, отличной от вызываемой лямбда. У него нет абсолютно никакой информации даже около , как она там попала.

Принимая во внимание, что исключения не исключают, что вы поймаете ловушку в блоке захвата в обратный вызов завершения, как в случае с D3().

Я использую компилятор C# 6.0 в Visual Studio Community 2015 Edition, а моя целевая программа v4.5.2 платформы .NET.

+0

@ChrisO я сделал, но это не точка здесь. Дело в том, что время выполнения ограничивало поток и продолжало процесс, таким образом терпя неудачу, что было не поведением, которое я ожидал. То, что я получил предупреждение, было только потому, что я явно бросил исключение. Я бы не выбрал это явно и назвал 'Console.ReadLine()' например и разделил случайное число на ввод пользователя и все равно получил либо «FormatException», либо «DivideByZeroException». Это не изменило бы вопрос, так как он избавился бы от предупреждения. –

+0

Вы правы, предупреждение не имеет большого значения для контекста вопроса. –

+1

Я тоже был этому удивлен. Я видел эту ссылку, которая может пролить свет: http://stackoverflow.com/questions/6465517/why-unhandled-exception-in-a-background-thread-doesnt-crash-the-app-domain Единственный раз, когда я видел что-то подобное было тогда, когда поток в службе C# Windows вызывал «GetDirectories», и наша паршивая сеть потеряла соединение с общим диском, на который смотрел поток. Вместо того, чтобы терпеть неудачу, поток просто застопорился, и служба продолжалась без каких-либо предупреждений. Довольно раздражает, поскольку поток должен был делать что-то важное. –

ответ

3

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

using System; 
using System.Runtime.Remoting.Messaging; 
using System.Threading; 

namespace OriginalCallStackIsLostOnRethrow 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      try 
      { 
       A2(); 
       // A3(); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex); 
      } 
      finally 
      { 
       Console.WriteLine("Press any key to exit..."); 
       Console.ReadKey(); 
      } 
     } 

     static void A2() { B2(); } 
     static void B2() { C2(); } 
     static void C2() { D2(); } 
     static void D2() 
     { 
      Action action =() => 
      { 
       Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}. Exception will occur while running D2"); 
       throw new DivideByZeroException();  
      }; 
      action.BeginInvoke(ar => 
      { 
       ((Action)((ar as AsyncResult).AsyncDelegate)).EndInvoke(ar); 

       Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}"); 
      }, null); 
     } 

     static void A3() { B3(); } 
     static void B3() { C3(); } 
     static void C3() { D3(); } 
     static void D3() 
     { 
      Action action =() => { Console.WriteLine($"D2 called on worker #{Thread.CurrentThread.ManagedThreadId}."); }; 
      action.BeginInvoke(ar => 
      { 
       try 
       { 
        Console.WriteLine($"D2 completed on worker thread #{Thread.CurrentThread.ManagedThreadId}. Oh, but wait! Exception!"); 
        throw new DivideByZeroException(); 
       } 
       catch (Exception ex) 
       { 
        throw ex; 
       } 
      }, null); 

     } 
    } 
} 

Это странно, и он до сих пор остается загадкой, почему трассировки стека не отображается, если вы должны были поместить try/catch блок в действии, которая выполняется асинхронно.

Я имею в виду отсутствие StackTrace, а не отсутствие стека вызовов. :-)

enter image description here

+0

Существует стек вызовов. Это просто, что ваш код делегата является первым фреймом, автором которого вы являетесь. Сделать Visual Studio внешним кодом. – usr

+0

@usr Конечно, стек вызовов * имеет *, чтобы быть там. Я имею в виду отсутствие трассировки стека. Пожалуйста, см. Картинку в обновленном ответе. :-) –

+0

@usr Я вижу, как я назвал свое пространство имен, возможно, вас обманул, чтобы подумать, что последний из моих меньших вопросов в этом вопросе был о стеке вызовов *. –

Смежные вопросы