如何正确地捕获WinForms应用程序中的所有未处理异常

5
我希望为我的WinForms应用程序中任何线程的所有未处理异常设置处理程序方法。我不会自己创建任何应用程序域。
根据UnhandledException文档,我需要通过Application.SetUnhandledExceptionMode方法设置UnhandledExceptionMode.ThrowException模式来捕获主线程的异常:
在使用Windows窗体的应用程序中,主应用程序线程中的未处理异常会导致引发Application.ThreadException事件。如果已处理此事件,则默认行为是未处理异常不会终止应用程序,尽管应用程序处于未知状态。在这种情况下,不会引发UnhandledException事件。可以通过使用应用程序配置文件或在挂钩ThreadException事件处理程序之前使用Application.SetUnhandledExceptionMode方法将模式更改为UnhandledExceptionMode.ThrowException来更改此行为。这仅适用于主应用程序线程。对于其他线程抛出的未处理异常,将引发UnhandledException事件。
因此,最终代码如下所示:
    public static void UnhandledExceptionEventHandler(object sender, UnhandledExceptionEventArgs e)
    {
        // ...
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionEventHandler);

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm(pathToCheck));
    }

可以吗?它会捕获所有未处理的异常,包括所有线程(包括主线程、UI线程和由Task类创建的所有线程)吗? 我是否正确理解了文档?

是的,我看到了像这个的问题,但我不明白为什么我还应该使用以下代码:

Application.ThreadException += new     
  ThreadExceptionEventHandler(ErrorHandlerForm.Form1_UIThreadException);

1
请参考这个答案,了解为什么您也需要处理ThreadException。 - stuartd
根据这个链接,我也可以通过SetUnhandledExceptionMode方法强制UI线程异常,所以我不需要指定ThreadException处理程序。我是对的吗? - FrozenHeart
由于第三方库的存在可能会导致异常情况,因此建议处理ThreadException。 - A G
不,ThreadException 的默认处理程序在13年前是一个相当不错的想法,可以让新接触 .NET 的程序员有机会处理异常而不至于失败。但那些日子已经过去了,期望用户在丑陋的对话框中转动幸运轮已经变得相当不合理。 - Hans Passant
@Hans Passant 所以我完全不需要担心ThreadException吗? - FrozenHeart
1个回答

5

您应该订阅这两个事件。请注意,即使这样,也不能自动捕获来自其他线程的所有内容。例如,当委托异步调用时,只有在调用 EndInvoke 时,异常才会传播到调用线程。

    [STAThread]
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException +=
            (sender, args) => HandleUnhandledException(args.ExceptionObject as Exception);
        Application.ThreadException +=
            (sender, args) => HandleUnhandledException(args.Exception);
    }

    static void HandleUnhandledException(Exception e)
    {
        // show report sender and close the app or whatever
    }

"你应该订阅这两个事件" -- 为什么? "只有在调用EndInvoke方法时,异常才会传播到调用线程" -- 如果我根本不显式调用EndInvoke方法呢? - FrozenHeart
为什么:因为它们可以捕获来自不同来源的异常。请参见https://msdn.microsoft.com/en-us/library/system.windows.forms.application.threadexception%28v=vs.110%29.aspxEndInvoke:通常情况下,您不需要担心这个问题,在已经工作的应用程序中,BeingInvokeEndInvoke总是成对调用的。否则,调用的结果也将丢失。请参见https://dev59.com/52s05IYBdhLWcg3wB9ef - György Kőszeg
我应该只在main方法中设置ThreadException吗?这样做是否会使此功能适用于所有未来的任务和线程? - FrozenHeart
如果您不使用多个“AppDomain”,那么是的,这就足够了。 - György Kőszeg
如果使用多个AppDomains,我应该把Application.ThreadException放在哪里? - Kiquenet
@Kiquenet:除非在子域中也创建表单,否则您不需要订阅此事件。否则,您可以像处理其他事件一样,在域的入口点订阅它。另一方面,处理AppDomain.UnhandledException总是一个好主意。域中的处理程序可以通知主机域有关未处理异常的信息,甚至可能应该卸载崩溃的域。 - György Kőszeg

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