简体   繁体   English

为什么此后台线程中的未处理异常不会终止我的进程?

[英]Why does an unhandled exception in this background thread not terminate my process?

I spawn a foreground thread and a background thread, throwing an exception in each. 我生成一个前台线程和一个后台线程,在每个线程中抛出一个异常。

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);
        }
    }
}

As expected, the unhandled exception in the foreground thread terminates the process. 正如所料,前台线程中的未处理异常终止了该进程。 However, the unhandled exception in the background thread just terminates the thread and does not bring the process to a halt, effectively going unobserved and failing silently. 但是,后台线程中的未处理异常只是终止线程,并且不会使进程停止,实际上无法观察到并且无声地失败。

This program, therefore, produces the following output: 因此,该程序产生以下输出:

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

This challenges my understanding about exception handling in threads. 这挑战了我对线程中异常处理的理解。 My understanding was that regardless of the nature of the thread, an unhandled exception, from v2.0 of the framework onwards, will bring the process to termination. 我的理解是,无论线程的性质如何,从框架的v2.0开始,未处理的异常将使进程终止。

Here is a quote from the documentation on this topic: 以下是有关此主题的文档的引用:

The foreground or background status of a thread does not affect the outcome of an unhandled exception in the thread. 线程的前台或后台状态不会影响线程中未处理的异常的结果。 In the .NET Framework version 2.0, an unhandled exception in either foreground or background threads results in termination of the application. 在.NET Framework 2.0版中,前台或后台线程中的未处理异常导致应用程序终止。 See Exceptions in Managed Threads. 请参阅托管线程中的例外。

Further more, the page titled Exceptions in Managed Threads states as follows: 此外,标题为“ 托管线程中的异常”的页面如下所示:

Starting with the .NET Framework version 2.0, the common language runtime allows most unhandled exceptions in threads to proceed naturally. 从.NET Framework 2.0版开始,公共语言运行库允许线程中大多数未处理的异常自然地继续。 In most cases this means that the unhandled exception causes the application to terminate. 在大多数情况下,这意味着未处理的异常会导致应用程序终止。

This is a significant change from the .NET Framework versions 1.0 and 1.1, which provide a backstop for many unhandled exceptions — for example, unhandled exceptions in thread pool threads. 这是.NET Framework版本1.0和1.1的重大变化,它为许多未处理的异常提供了支持 - 例如,线程池线程中的未处理异常。 See Change from Previous Versions later in this topic. 请参阅本主题后面的“从先前版本更改”。

ANOTHER INTERESTING OBSERVATION 另一个有趣的观察

Interestingly, if I cause the exception to be thrown in the completion callback instead of the actual action that is being done, the exception on the background thread in that case does cause a termination of the program. 有趣的是,如果我在完成回调中抛出异常而不是正在执行的实际操作,那么后台线程上的异常确实会导致程序终止。 For code, please see below. 有关代码,请参阅下文。

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);
        }
    }
}

YET ANOTHER INTERESTING OBSERVATION 另一个有趣的观察

Further, even more interestingly, if you handle the exception in the action that you want to execute using APM, in the catch block (set a breakpoint in the catch block in D2() ), the Exception that appears has no stack trace other than the lambda being invoked. 此外,更有趣的是,如果您在要使用APM执行的操作中处理异常,则在catch块中(在D2()中的catch块中设置断点),出现的Exception除了被调用的lambda。 It has absolutely no information even about how it got there . 它甚至没有关于它是如何到达那里的信息

Whereas this is not true for exceptions that you trap in a catch block in the completion callback , as in the case of D3() . 然而,对于在完成回调中捕获在catch块中异常,情况并非如此,如D3()的情况。

I am using the C# 6.0 compiler in Visual Studio Community 2015 Edition and my program targets v4.5.2 of the .NET framework. 我在Visual Studio Community 2015 Edition中使用C#6.0编译器,我的程序目标是.NET框架的v4.5.2。

As PetSerAl points out in the comments section of the question, to get the exception information, it is mandatory to call EndInvoke from inside the completion callback as shown below. 正如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);

        }
    }
}

This is weird, and it still remains a mystery as to why the stack trace does not show up if you were to place a try / catch block in the action that is executing asynchronously. 这很奇怪,如果你要在异步执行的动作中放置一个try / catch块,为什么堆栈跟踪不显示仍然是个谜。

I am referring to the absence of the StackTrace , not the absence of a call stack. 我指的是没有StackTrace ,而不是没有调用堆栈。 :-) :-)

在此输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM