.NET - 如何最佳实现“捕获所有异常处理程序”?

77

我在想,最好的方式是什么来处理“如果其他所有方法都失败了”。

我的意思是,在你的应用程序中尽可能地处理异常,但仍然会有错误,所以我需要有一个能够捕获所有未处理异常的方式,以便我可以收集信息并将它们存储到数据库或提交到 Web 服务中。

AppDomain.CurrentDomain.UnhandledException 事件能够捕获所有异常吗?即使应用程序是多线程的?

顺带一提:Windows Vista 公开了本机 API 函数,允许任何应用程序在崩溃后恢复自身...现在想不起名字了...但我宁愿不使用它,因为我们的许多用户仍在使用 Windows XP。


2
这是Windows Vista中的“重启管理器”功能:http://www.danielmoth.com/Blog/2006/10/vista-restart-manager.html - huseyint
4
我写了一篇详细的博客文章,标题为《在WinForms中使用.NET进行最后机会异常处理:必须全部捕获》。链接为http://jonathonreinhart.blogspot.com/2013/03/gotta-catch-em-all-last-chance.html。 - Jonathon Reinhart
9个回答

35

我刚刚尝试了一下AppDomain的UnhandledException行为(这是未处理异常注册的最后阶段)

是的,在处理完事件处理程序之后,你的应用程序将被终止,并显示讨厌的“...程序停止工作”对话框。

:) 你仍然可以避免这种情况。

查看:

class Program
{
    void Run()
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

        Console.WriteLine("Press enter to exit.");

        do
        {
            (new Thread(delegate()
            {
                throw new ArgumentException("ha-ha");
            })).Start();

        } while (Console.ReadLine().Trim().ToLowerInvariant() == "x");


        Console.WriteLine("last good-bye");
    }

    int r = 0;

    void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Interlocked.Increment(ref r);
        Console.WriteLine("handled. {0}", r);
        Console.WriteLine("Terminating " + e.IsTerminating.ToString());

        Thread.CurrentThread.IsBackground = true;
        Thread.CurrentThread.Name = "Dead thread";            

        while (true)
            Thread.Sleep(TimeSpan.FromHours(1));
        //Process.GetCurrentProcess().Kill();
    }

    static void Main(string[] args)
    {
        Console.WriteLine("...");
        (new Program()).Run();
    }
}

P.S.一定要在更高的层次上处理Application.ThreadException(WinForms)或DispatcherUnhandledException(WPF)中未处理的异常。


21
我认为提一下是公平的,上面的代码会让每个失败的线程一直存在。也就是说,早晚你会有很多僵尸线程挂着。个人觉得这没什么用。 - Brian Rasmussen
2
我同意。这样做会产生僵尸线程。虽然感到悲伤,但我接受这种方式,以免在处理程序退出后立即杀死我的应用程序。毕竟,在让死亡的线程永远睡眠之前,您可以发送报告到您的服务器,让用户完成手头的任务并优雅地退出(或重新启动应用程序)。 - bohdan_trotsenko
3
这个解决方案在某些情况下是非常错误的,但同时也是非常必要的 :) +1 - empi
我可以在ASP.NET应用程序中使用AppDomain.CurrentDomain.UnhandledException吗?这是一个好的模式和实践吗? - Kiquenet
1
如果线程的名称已经被设置过,则此代码将引发异常:Thread.CurrentThread.Name = "死亡线程" - FunctorSalad
显示剩余6条评论

18
在ASP.NET中,您可以在Global.asax文件中使用Application_Error函数。
在WinForms中,您可以在ApplicationEvents文件中使用MyApplication_UnhandledException函数。
这两个函数都会在代码出现未处理的异常时被调用。您可以在这些函数中记录异常并向用户呈现友好的消息。

13

6
除了次要线程、计时器线程和线程池线程以外,一切都包括在内! - Steven A. Lowe
2
我同意,如果“处理异常”被理解为实际上采取措施来恢复,那么AppDomain的“UnhandledException”是不够的。然而,原问题似乎只是针对错误报告而不是恢复。仅为此目的它就足够了。 - Christian.K
1
@Steven:如果通过BeginInvoke启动线程池作业,则该线程中的任何异常在调用EndInvoke时都会被传输到调用线程。 - Brian Rasmussen

7
在主线程中,您有以下选项:

对于其他线程:

  • 辅助线程没有未处理的异常;使用 SafeThread
  • 工作线程(定时器、线程池)没有安全网!

请记住,这些事件不会处理异常,它们仅仅将异常报告给应用程序--通常在处理它们已经太晚/不可行的时候。

记录异常很好,但监视应用程序更好;-)

注意:我是SafeThread文章的作者。


@StevenA.Lowe 您没有关于 CALM 的完整源代码备份吗? - Kiquenet

4

请提供“当前线程未处理异常事件”的参考链接,我原以为这样的事件不存在! - Steven A. Lowe
未找到http://www.codeproject.com/KB/architecture/exceptionbestpractices.aspx相关内容。 - Kiquenet

2
还有一个很酷的东西叫做 ELMAH,它可以记录 Web 应用程序中发生的任何 ASP.NET 错误。我知道你在询问 Winform 应用程序的解决方案,但我认为这对于需要在 Web 应用程序上使用此类功能的任何人都会有所裨益。我们在我工作的地方使用它,在调试方面非常有帮助(特别是在生产服务器上!)
以下是它具有的一些功能(从页面上提取):
  • 记录几乎所有未处理的异常。
  • 一个网页可以远程查看已记录异常的完整日志。
  • 一个网页可以远程查看任何一个已记录异常的完整详细信息。
  • 在许多情况下,即使关闭了 customErrors 模式,您也可以查看 ASP.NET 为给定异常生成的原始黄屏幕。
  • 每次错误发生时都会收到电子邮件通知。
  • 最近 15 个错误的 RSS 订阅。
  • 日志的多个后备存储实现,包括内存、Microsoft SQL Server 和社区贡献的若干种实现。

虽然我问的是非网络应用程序,但这对其他人仍然是非常有用的信息!所以+1。 - TimothyP

2

即使在多线程应用程序中,你可以在该处理程序中监视大多数异常,但.NET(从2.0开始)不允许您取消未处理的异常,除非您启用1.1兼容模式。当这种情况发生时,无论如何,应用程序域将被关闭。你能做的最好的事情就是在不同的应用程序域中启动应用程序,以便你可以处理此异常并创建一个新的应用程序域来重新启动应用程序。


0
在一个托管的GUI应用程序中,默认情况下,源自GUI线程的异常由分配给Application.ThreadException的任何内容处理。
源自其他线程的异常由AppDomain.CurrentDomain.UnhandledException处理。
如果您希望您的GUI线程异常与非GUI异常一样工作,以便它们由AppDomain.CurrentDomain.UnhandledException处理,您可以执行以下操作:
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);

使用ThreadException捕获GUI线程异常的优点是可以让用户选择让应用程序继续运行。为了确保没有配置文件覆盖默认行为,您可以调用:
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

您仍然容易受到行为不良的本地 dll 引起的异常影响。如果一个本地 dll 使用 Win32 的 SetUnhandledExceptionFilter 安装了自己的处理程序,它应该保存前一个过滤器的指针并调用它。如果没有这样做,您的处理程序将不会被调用。


0
我正在使用以下方法,它可以工作并大大减少代码量(但我不确定是否有更好的方法或可能存在的问题)。每当您调用时: 我猜那些给负面评价的人会很客气地澄清他们的行为;)
try 
{
    CallTheCodeThatMightThrowException()
 }
catch (Exception ex)
{
    System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace ();
    Utils.ErrorHandler.Trap ( ref objUser, st, ex );
} //eof catch

以下是ErrorHandler代码: 为了明确一点:objUser是模拟应用程序用户的对象(您可以获取诸如域名、部门、区域等信息以进行日志记录) ILog logger是日志记录对象,例如执行日志记录活动的对象 StackTrace st是提供应用程序调试信息的StackTrace对象

using System;
using log4net; //or another logging platform

namespace GenApp.Utils
{
  public class ErrorHandler
  {
    public static void Trap ( Bo.User objUser, ILog logger, System.Diagnostics.StackTrace st, Exception ex )
    {
      if (ex is NullReferenceException)
      { 
      //do stuff for this ex type
      } //eof if

      if (ex is System.InvalidOperationException) 
      {
        //do stuff for this ex type
      } //eof if

      if (ex is System.IndexOutOfRangeException) 
      {
        //do stuff for this ex type
      } //eof if

      if (ex is System.Data.SqlClient.SqlException)
      {
        //do stuff for this ex type
      } //eof if

      if (ex is System.FormatException)
      {
        //do stuff for this ex type
      } //eof if

      if (ex is Exception)
      {
        //do stuff for this ex type
      } //eof catch

    } //eof method 

  }//eof class 
} //eof namesp

这只会在当前线程上捕获异常。即使CallTheCodeThatMightThrowException创建或使用其他线程,它也没有什么用处。 - Brian Rasmussen

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