Я создаю переднюю нить и фоновый поток, бросая в них исключение.Почему необработанное исключение в этом фоновом потоке не завершает мой процесс?
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.
@ChrisO я сделал, но это не точка здесь. Дело в том, что время выполнения ограничивало поток и продолжало процесс, таким образом терпя неудачу, что было не поведением, которое я ожидал. То, что я получил предупреждение, было только потому, что я явно бросил исключение. Я бы не выбрал это явно и назвал 'Console.ReadLine()' например и разделил случайное число на ввод пользователя и все равно получил либо «FormatException», либо «DivideByZeroException». Это не изменило бы вопрос, так как он избавился бы от предупреждения. –
Вы правы, предупреждение не имеет большого значения для контекста вопроса. –
Я тоже был этому удивлен. Я видел эту ссылку, которая может пролить свет: http://stackoverflow.com/questions/6465517/why-unhandled-exception-in-a-background-thread-doesnt-crash-the-app-domain Единственный раз, когда я видел что-то подобное было тогда, когда поток в службе C# Windows вызывал «GetDirectories», и наша паршивая сеть потеряла соединение с общим диском, на который смотрел поток. Вместо того, чтобы терпеть неудачу, поток просто застопорился, и служба продолжалась без каких-либо предупреждений. Довольно раздражает, поскольку поток должен был делать что-то важное. –