为啥不能在主线程中直接捕获和处理工作线程抛出的异常?

Posted

技术标签:

【中文标题】为啥不能在主线程中直接捕获和处理工作线程抛出的异常?【英文标题】:Why is it not possible to directly catch and handle an exception thrown from a worker thread in the Main thread?为什么不能在主线程中直接捕获和处理工作线程抛出的异常? 【发布时间】:2021-03-27 02:27:40 【问题描述】:

简而言之,我正在阅读 Albahari 的 c# 中的多线程。他说,如果一个线程(例如主线程)创建并启动了一个工作线程,那么工作线程抛出的异常不能直接被创建线程捕获和处理。让我逐字引用他:

"任何在创建线程时生效的 try/catch/finally 块都是无效的 线程开始执行时的相关性。考虑以下 程序:

    public static void Main() 
    
        try
        
            Thread workerThread = new Thread (Go);
            workerThread.Start(); 
        

        catch (Exception ex)
          
           // We'll never get here!
           Console.WriteLine ("Exception!");
        
    


    static void Go() 
     
       
        throw null;     // Throws a NullReferenceException

     

阿尔巴哈里接着说: “本例中的 try/catch 语句无效,新的 创建的线程将受到未处理的阻碍 空引用异常。当您考虑时,这种行为是有意义的 每个线程都有独立的执行路径。”

所以这是我问题的症结所在:

我不明白“每个线程都有独立的执行路径”的相关性。我的意思是,如果执行路径是独立的,为什么重要?我的意思是当workerThread 抛出一个未处理的异常时——为什么CLR 不能停止主线程并将异常对象交给主线程中的catch 块?是什么阻止了它??

[注:

    另一个相关问题How do I handle exceptions from worker threads and main thread in a single catch block? 不是在问同样的问题 --- 并且没有一个详尽的答案表达了为什么 CLR 不能将未处理的异常从工作线程封送到主线程 关于另一种语言 C++ 的类似问题 - 答案表明它与具有不同堆栈的两个线程有​​关,并且在异常处理期间展开堆栈时混合两者的逻辑不可能。我不确定这些答案是否适用于托管执行环境,例如 c#。 ]

【问题讨论】:

想想两条或更多平行道路之类的线程。如果一条道路上发生交通事故,为什么另一条道路上的汽车应该停下来? 【参考方案1】:

主线程可能已经执行完毕。

我知道一开始很难理解,但是即使看起来像方法调用,工作线程也不会像方法调用那样执行。该线程是在该 try 块内创建的,但执行可能会更晚发生或根本不会发生。文本形式的源代码无法使其可见。 try-block 和线程之间似乎存在某种“空间”接近,但是在创建线程的那一刻,空间接近就消失了。 try 块只处理线程创建时发生的任何异常,但它与它“分离”。

类比:主线程是经理(团队负责人),工作线程是向该经理报告的工作人员(团队成员)。工人们正在他们的家庭办公室工作。早上,经理向他们发送包含当天任务的电子邮件(“执行方法 Go”)。由于经理看不到工人在做他们的工作,她只能在工人不时发送进度报告的情况下注意到任何进展或不足。如果工人从椅子上摔下来或从楼梯上摔下来(例外),经理不会知道。工作人员需要确保捕获此类异常并向经理发送适当的消息让她知道。经理(在这种情况下)在发送初始电子邮件后没有等待,而是在此期间做其他工作。

【讨论】:

【参考方案2】:

您在问为什么 CLR 不能停止主线程并处理子线程的异常。您的意思是主线程应该总是在启动新的Thread 后立即自动暂停执行吗?这将非常没有帮助,因为一个程序最多只能有一个且只有一个活动线程。正如我们所知,这将是多线程的终结!因此,您可能的意思是 CLR 应该提供一种机制来按需暂停执行。虽然这样的机制确实不可用,但你可以使用任务做一些非常接近它的事情:

public static void Main()

    try
    
        Task workerTask = new Task(() =>
        
            throw null;
        , TaskCreationOptions.LongRunning); // Use a dedicated thread
        workerTask.Start();
        workerTask.Wait();
    
    catch (Exception ex)
    
        Console.WriteLine("Exception!"); // It happens!
    

【讨论】:

以上是关于为啥不能在主线程中直接捕获和处理工作线程抛出的异常?的主要内容,如果未能解决你的问题,请参考以下文章

Java技术指南「技术盲区」看看线程以及线程池的异常处理机制都有哪些?

Handler的工作原理。为啥在子线程中使用Handler会抛出异常

捕获Java线程池执行任务抛出的异常

捕获在不同线程中抛出的异常

怎样在python中捕获线程抛出的异常

主线程会捕获另一个线程抛出的异常吗?