【问题标题】:Why unhandled exception in a background thread doesnt crash the app domain?为什么后台线程中未处理的异常不会使应用程序域崩溃?
【发布时间】:2011-09-21 20:55:45
【问题描述】:

我完全不解。如果我从未测试过的线程中存在未捕获的异常,我非常确定 .NET 会关闭整个应用程序域。

但是我刚刚尝试了以下代码,但它并没有失败...谁能解释一下原因?

(在 .NET 4 和 3.5 中尝试过)

static void Main(string[] args)
{
    Console.WriteLine("Main thread {0}", Thread.CurrentThread.ManagedThreadId);

    Action a = new Action(() =>
    {
        Console.WriteLine("Background thread {0}", Thread.CurrentThread.ManagedThreadId);

        throw new ApplicationException("test exception");
    });

    a.BeginInvoke(null, null);

    Console.ReadLine();
}

【问题讨论】:

    标签: c# .net multithreading exception-handling delegates


    【解决方案1】:

    发生这种情况是因为BeginInvoke 在内部使用ThreadPool 并且当ThreadPool 任何未处理的异常将静音失败。但是,如果您使用a.EndInvoke,则未处理的异常将在EndInvoke 方法中抛出。

    注意:正如João Angelo 所说,直接使用ThreadPool 方法“如ThreadPool.QueueUserWorkItemsUnsafeQueueUserWorkItem”会在2.0 及更高版本抛出异常。

    【讨论】:

    • -1:您的回答还表明,使用引发异常的方法调用ThreadPool.QueueUserWorkItem 会导致异常被框架吃掉,这在 2.0 及更高版本中并非如此。跨度>
    • @João Angelo:线程池一般不会抛出异常,例如新的 C# 4.0 任务不会抛出异常,而是在 tast.Wait 方法中,就像 EndInvoke 方法一样。
    • 再次强调,这是任务并行库的一个特性,而不是线程池的特性。直接尝试使用线程池,异常不会被静默。
    【解决方案2】:

    来自 MSDN 上的Exceptions in Managed Threads

    在 .NET Framework 2.0 版中, 公共语言运行时允许大多数 线程中未处理的异常 自然地进行。在大多数情况下,这 表示未处理的异常 导致应用程序终止。

    这是一个显着的变化 .NET Framework 1.0 和 1.1 版, 这为许多人提供了支持 未处理的异常——例如, 线程池中未处理的异常 线程。见从以前的变化 本主题后面的版本。

    作为临时的兼容性措施, 管理员可以放置一个 兼容性标志 应用程序部分 配置文件。这导致 要恢复到的公共语言运行时 1.0 和 1.1 版本的行为。

    <legacyUnhandledExceptionPolicy enabled="1"/>
    

    【讨论】:

    • 乔恩感谢您的努力,但您没有阅读问题。问题正是为什么它没有失败。干杯
    • 我理解了这个问题,我告诉你官方的指导是什么,并给你一些可能的原因(框架版本,配置设置),以及一些进一步阅读以了解发生了什么:)
    • @Bobb:你总是如何回应那些试图免费帮助你的陌生人,这是一种尖锐的批评吗?这对你有什么影响?
    【解决方案3】:

    通常使用异步委托,如果委托方法抛出异常,线程将被终止,并且该异常将在调用代码中再次抛出当您调用EndInvoke时。

    这就是为什么在使用异步委托 (BeginInvoke) 时,您应该始终调用 EndInvoke。另外,这不应该与Control.BeginInvoke 混淆,后者可以以一种即发即弃的方式调用。

    之前我说的是正常的,因为您有可能声明如果委托方法返回 void,则应忽略异常。为此,您需要使用OneWay 属性标记该方法。

    如果你运行下面的例子,你只会在调用willNotIgnoreThrow.EndInvoke时得到一个异常。

    static void Throws()
    {
        Console.WriteLine("Thread: {0}", Thread.CurrentThread.ManagedThreadId);
    
        throw new ApplicationException("Test 1");
    }
    
    [OneWay]
    static void ThrowsButIsIgnored()
    {
        Console.WriteLine("Thread: {0}", Thread.CurrentThread.ManagedThreadId);
    
        throw new ApplicationException("Test 2");
    }
    
    static void Main(string[] args)
    {
        Console.WriteLine("Main: {0}", Thread.CurrentThread.ManagedThreadId);
    
        var willIgnoreThrow = new Action(ThrowsButIsIgnored);
        var result1 = willIgnoreThrow.BeginInvoke(null, null);
    
        Console.ReadLine();
        willIgnoreThrow.EndInvoke(result1);
    
        Console.WriteLine("============================");
    
        var willNotIgnoreThrow = new Action(Throws);
        var result2 = willNotIgnoreThrow.BeginInvoke(null, null);
    
        Console.ReadLine();
        willNotIgnoreThrow.EndInvoke(result2);
    }
    

    【讨论】:

    • @The Anonymous Downvoter,请解释一下为什么您认为这不能回答 OP 问题?
    • 我能得出的最佳结论是 Bobb 将所有问题判断为 +1 或 -1,并且无法理解反对票的含义
    【解决方案4】:

    因为给定线程上的异常抛出会保留在那里,除非它被引导回主线程。

    这就是 backgroundWorker 为你做的事情,如果 BackgroundWorker 的线程中有异常,它会在主线程上重新抛出。

    a.BeginInvoke(null, null);
    

    这会进行异步调用,这会创建另一个线程来执行它

    【讨论】:

    • 您正在混淆一个“后台线程”和 BackgroundWorker 类。
    • 不是,BackgroundWorker 创建了后台线程。普通线程和后台线程的唯一区别是后台线程不会阻止您的应用程序关闭,而您必须等待普通线程完成其工作。
    猜你喜欢
    • 1970-01-01
    • 2012-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-13
    • 2019-01-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多