为什么在C#中,线程池线程上引发的异常不会由主线程处理

3
我有以下代码片段。
class Program
{
    static void Main(string[] args)
    {
        try
        {
            Thread m_thread = new Thread(() =>
                {
                    //try
                    //{
                        checkexc();
                    //}
                    //catch (Exception ex)
                    //{

                    //}
                }
                );               
            m_thread.Start();

        }
        catch (Exception ex)
        {

        }
    }
    static void checkexc()
    {
        throw new NullReferenceException();
    }
}

当使用外部的Try-Catch块时,NullReferenceException异常不会被处理。但是,如果我将委托包装在thread()构造函数中,则该异常将由该Try-Catch块处理。为什么外部的Try-Catch块无法处理此异常。


7
因为它是一个不同的线程。 - ColWhi
6个回答

7

想象一下你有一条主路(A),还有一条从该路分支出来的另一条路(B)。

当一辆卡车沿着A行驶时,如果发生事故,则A将知道,并且交通将停止。

当一辆卡车沿着B行驶时,如果发生事故,则B将知道,并且交通将停止。

但是B如何告诉A卡车在其上发生了碰撞?

一旦卡车在B上,除非A中有另一个入口进入B,否则它不会影响A。

您其他线程中的异常将如何与主线程通信?一旦其他线程正在运行,它就不再(直接)与主线程通信。


是的,这是一个泄漏的抽象。 - Matt Ellen

1
从另一个角度来回答这个问题:
任务并行库(TPL)确实为您处理(捕获和传播)异常。
但是,异常出现在您等待任务完成的点上,而不是您启动任务的点/线程上。
    // No point in specifically surrounding the next 2 statements
    Task t1 = Task.Factory.StartNew(() => Foo());
    Task t2 = Task.Factory.StartNew(() => Bar());

    try
    {
        Task.WaitAll(t1, t2);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);  // Aggregated exceptions from Foo and/or Bar
    }

当使用 Parallel.ForEach() 时,代码看起来像你的,因为在 ForEach() 结束时有一个隐式的 WaitAll()。


1
如Matt所述,子线程抛出的异常不会冒泡到父线程。但是,如果您想要能够捕获来自子线程的异常,这里有一种方法:
class Program {
    static void Main(string[] args) {
        Action action = BeginCheckExc;
        IAsyncResult result = action.BeginInvoke(new AsyncCallback(EndCheckExc), null);

        try {
            action.EndInvoke(result);
        }
        catch (Exception ex) { // Exception is caught here
            Console.WriteLine(ex.Message);
        }
    }

    static void BeginCheckExc() {
        Thread.Sleep(3000); // Simulate long operation
        throw new Exception("Oops! Something broke!");
    }

    static void EndCheckExc(IAsyncResult result) {
        Console.WriteLine("Done");
        Console.ReadLine();
    }
}

你将看到的输出类似于:

完成。

哎呀!出了点问题!

按任意键继续...


1

因为异常没有发生在线程构造函数中。checkexc()只有在调用m_thread.Start()之后才被调用,并且它在另一个线程上执行。


0

如果您有兴趣从所有线程中捕获未处理的异常,您可以添加一个应用程序范围的未处理异常处理程序。请查看 Application.ThreadExceptionAppDomain.UnhandledException 的文档。

实际上,您需要处理此问题的代码如下:

Application.ThreadException += new ThreadExceptionEventHandler(ThreadExceptionFunction);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionFunction);

public static void ThreadExceptionFunction(object sender, ThreadExceptionEventArgs args)
{
     // Handle the exception.          
}

public static void UnhandledExceptionFunction(object sender, UnhandledExceptionEventArgs args)
{
     // Handle the exception. 
}

请记住,异常发生后,您的应用程序可能处于损坏状态,因此最好在记录错误后尽快退出。


0

正如其他人所说,“因为它在不同的线程中”。或者换句话说,为什么第一个线程应该因为另一个线程遇到异常而受到干扰呢?

如果您使用BackgroundWorker,则异常会在参数中传递给RunWorkerCompleted事件(当线程完成时)-这可能会更容易,具体取决于您的情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接