为什么在后台线程中未处理的异常不会导致应用程序域崩溃?

16

我感到十分困惑。我一直相信,如果在线程中有未捕获的异常,.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();
}
4个回答

10

这是因为 BeginInvoke 内部使用了 ThreadPool,如果 ThreadPool 没有处理异常,则会静默失败。但是,如果您使用 a.EndInvoke,则未处理的异常将在 EndInvoke 方法中抛出。

注意:正如 João Angelo 所述,直接使用 ThreadPool.QueueUserWorkItemsUnsafeQueueUserWorkItemThreadPool 方法将在 2.0 及以上版本中引发异常。


4
你的回答表明,在使用 ThreadPool.QueueUserWorkItem 方法并传递一个会抛出异常的方法时,异常将被框架捕获掉,但实际上在2.0及之后的版本中并不是这样。 - João Angelo
1
@João Angelo:ThreadPool通常不会抛出异常,例如新的C# 4.0任务不会抛出异常,但在tast.Wait方法中就像EndInvoke方法一样。 - Jalal Said
1
再次强调,这是任务并行库的一个特性,而不是线程池的特性。如果直接使用线程池,异常将不会被抑制。 - João Angelo

6

来自MSDN上的托管线程中的异常:

In the .NET Framework version 2.0, the common language runtime allows most unhandled exceptions in threads to proceed naturally. 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. See Change from Previous Versions later in this topic.

As a temporary compatibility measure, administrators can place a compatibility flag in the section of the application configuration file. This causes the common language runtime to revert to the behavior of versions 1.0 and 1.1.

<legacyUnhandledExceptionPolicy enabled="1"/>

Jon,感谢你的努力,但你没有仔细阅读问题。问题确切地是为什么它没有失败。干杯 - Boppity Bop
8
我理解了这个问题,我会告诉你官方指南的内容,并为你提供一些可能的原因(框架版本、配置设置),以及进一步阅读资料来了解发生了什么。 - Jon Grant
24
@Bobb: 你总是对试图免费帮助你的陌生人发表尖酸刻薄的批评吗?这种方式对你有什么好处吗? - Eric Lippert

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

@匿名的投票者,您能解释一下为什么您认为这个回答没有回答到问题吗? - João Angelo
据我所知,Bobb最好的做法是将所有问题评为+1或-1,并未理解downvote的用途。 - Kieren Johnstone

0

因为在给定的线程上引发的异常会一直停留在那里,除非将其通道回主线程。

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

a.BeginInvoke(null, null);

这会进行异步调用,创建另一个线程来执行此操作。


你把“后台线程”和BackgroundWorker类混淆了。 - Boppity Bop
并不是真的,BackgroundWorker创建了后台线程。正常线程和后台线程之间唯一的区别在于,后台线程不会阻止应用程序关闭,而你必须等待正常线程完成其工作。 - Bek Raupov

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