在另一个线程中抛出异常时,未处理的异常未被调用

8
根据 Microsoft 文档,在线程池或使用 System.Threading.Thread 类创建的线程发生未处理异常时,应用程序的默认 AppDomain 应触发 AppDomain.UnhandledException 事件。这是 MSDN link 在第二个注意事项后解释的内容。
但是,我无法重现此行为。据我测试应用程序所示,它从未在默认 AppDomain 或用于创建线程的 AppDomain 上触发 UnhandledException。文档有误还是我的测试代码有问题?
using System;
using System.Runtime.ExceptionServices;
using System.Reflection;

public class Program
{
    static void Main()
    {
        Program.HookAppDomainExceptions();
        Test t = CreateTestInsideAppDomain("Nested1");
        t.SetupNested1();
        Console.ReadLine();
    }

    public static Test CreateTestInsideAppDomain(string appDomainName)
    {
        AppDomain nested1 = AppDomain.CreateDomain(appDomainName);
        string executingName = Assembly.GetExecutingAssembly().FullName;
        return (Test)nested1.CreateInstanceAndUnwrap(executingName, "Test");
    }

    public static void HookAppDomainExceptions()
    {
        AppDomain.CurrentDomain.FirstChanceException +=
            new EventHandler<FirstChanceExceptionEventArgs>(FirstChanceException);

        AppDomain.CurrentDomain.UnhandledException +=
            new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
    }

    public static void FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
    {
        Console.WriteLine("Domain:{0} FirstChanceException Handler",
                          AppDomain.CurrentDomain.FriendlyName);
    }

    public static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("Domain:{0} UnhandledException Handler",
                          AppDomain.CurrentDomain.FriendlyName);
    }
}

public class Test : MarshalByRefObject
{
    private delegate void Nothing();

    public void SetupNested1()
    {
        var start = new Nothing(Nested1ThreadStart);
        start.BeginInvoke(null, null);
    }

    static void Nested1ThreadStart()
    {
        Program.HookAppDomainExceptions();
        Test t = Program.CreateTestInsideAppDomain("Nested2");
        t.SetupNested2();
    }

    public void SetupNested2()
    {
        Program.HookAppDomainExceptions();
        Test t = Program.CreateTestInsideAppDomain("Nested3");
        t.ThrowException();
    }

    public void ThrowException()
    {
        Program.HookAppDomainExceptions();
        throw new ApplicationException("Raise Exception");
    }
}
2个回答

12

在你的代码中,UnhandledException 不会在任何 AppDomain 上触发,因为如果你使用 BeginInvoke() 调用委托,其执行期间抛出的任何异常都将被处理,然后在调用 EndInvoke() 时重新抛出,而你没有这样做。

如果你调用了 EndInvoke()

start.EndInvoke(start.BeginInvoke(null, null));

或者同步执行委托:

start();

您会得到类似的结果:UnhandledException 在主域中被引发。

如果按照文档所述,使用Thread类启动一个新线程:

new Thread(Nested1ThreadStart).Start();

未处理的 Nested1 异常和主应用程序域被引发。

因此,回答你的问题:文档是正确的。你的代码是错误的。当你使用 BeginInvoke() 异步调用委托时,你应该后续总是调用 EndInvoke()


2
我更倾向于使用 start.BeginInvoke(iar => start.EndInvoke(iar), null),因为你的用法会使方法调用同步。不过,回答非常好! - Jonathan Dickinson
@Jonathan,是的,那也可以,但它会改变行为。它在调用start的线程上调用EndInvoke(),这意味着它会在Nested1(和主域)上引发UnhandledException - svick
@svick - EndInvoke(BeginInvoke) 没有意义,除非创建一个本来就是异步的方法的同步版本。 - Jonathan Dickinson
@Jonathan,在实际应用中是没有的。但这不是实际应用,只是用来弄清楚“UnhandledException”行为的代码。 - svick

-1
我也曾经遇到过这个问题。我使用了观察者模式来解决它。 在你的调用类中,你可以实现一个接口,里面有一个方法,当另一个线程发生异常时会调用它。
这里有一个链接展示如何实现这个模式:探索观察者设计模式

这不是这个问题的重点。原帖作者(可能)知道如何处理异常,如果他想要(或能够)这样做的话。问题是未处理的异常会发生什么。 - svick

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