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

Wat*_* v2 5 .net c# multithreading asynchronous

我生成一个前台线程和一个后台线程,在每个线程中抛出一个异常.

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如所料,前台线程中的未处理异常终止了该进程.但是,后台线程中的未处理异常只是终止线程,并且不会使进程停止,实际上无法观察到并且无声地失败.

因此,该程序产生以下输出:

Press any key to exit...
D2 called on worker #6. Exception will occur while running D2
D2 completed on worker thread #6
Run Code Online (Sandbox Code Playgroud)

这挑战了我对线程中异常处理的理解.我的理解是,无论线程的性质如何,从框架的v2.0开始,未处理的异常将使进程终止.

以下是有关此主题的文档的引用:

线程的前台或后台状态不会影响线程中未处理的异常的结果.在.NET Framework 2.0版中,前台或后台线程中的未处理异常导致应用程序终止.请参阅托管线程中的例外.

此外,标题为" 托管线程中的异常"的页面如下所示:

从.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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另一个有趣的观察

此外,更有趣的是,如果您在要使用APM执行的操作中处理异常,则在catch块中(在catch块中设置断点D2()),Exception除了被调用的lambda之外,没有堆栈跟踪.它甚至没有关于它是如何到达那里的信息.

然而,对于在完成回调中捕获在catch块中异常,情况并非如此,例如D3().

我在Visual Studio Community 2015 Edition中使用C#6.0编译器,我的程序目标是.NET框架的v4.5.2.

Wat*_* v2 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);

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

try / catch这很奇怪,并且如果您要在异步执行的操作中放置一个块,为什么堆栈跟踪不会显示仍然是一个谜。

我指的是缺少 StackTrace 而不是缺少调用堆栈。:-)

在此输入图像描述