在 C#.NET 中捕获所有未处理异常的简单方法

Posted

技术标签:

【中文标题】在 C#.NET 中捕获所有未处理异常的简单方法【英文标题】:Easy way to catch all unhandled exceptions in C#.NET 【发布时间】:2010-09-22 16:23:56 【问题描述】:

我有一个用 C#.NET 构建的网站,它倾向于从各种用户控件中产生相当稳定的 SQL 超时流,我想轻松地弹出一些代码来捕获所有未处理的异常并将它们发送到可以记录它们的东西并向用户显示友好的消息。

我如何通过最小的努力捕获所有未处理的异常?

this question 似乎说这是不可能的,但这对我来说没有意义(它是关于 Windows 应用程序中的 .NET 1.1):

【问题讨论】:

这不是治标不治本吗?也就是说,您不应该通过在数据库服务器上运行 SQL Profiler 跟踪来查看 SQL 超时的原因吗? 我认为您需要澄清“catch”的含义:如 try..catch 中您可以处理异常的地方,或者如果您只想知道发生了未处理的异常,并记录地点/时间(例如 ELMAH) 你能把其中一个答案标记为正确的吗? 【参考方案1】:

所有未处理的异常最终都通过 global.asax 中的 Application_Error。因此,要提供一般异常消息或进行日志记录操作,请参阅Application_Error。

【讨论】:

看第一句话; “我有一个用 C#.NET 构建的网站,它倾向于......” 致任何想弄清楚前一条评论在说什么的人——我假设它正在回答现在已删除的评论。 (提示:看看谁写了答案,谁写了评论。) 您可能需要在 web.config 中设置<customErrors mode="Off"/>。如果没有此条目,它就无法在我的生产服务器上运行。【参考方案2】:

如果您需要在所有线程中捕获异常,最好的方法是实现 UnhandledExceptionModule 并将其添加到您的应用程序中,请查看 here 举个例子

【讨论】:

问题指的是 Windows 应用程序,而不是 Web 应用程序。 "我有一个用 C#.NET 构建的网站"【参考方案3】:

在 Global.asax 文件中使用 Application_Error 方法。在您的 Application_Error 方法实现调用 Server.GetLastError() 中,根据需要记录 Server.GetLastError() 返回的异常的详细信息。

例如

void Application_Error(object sender, EventArgs e) 
 
    // Code that runs when an unhandled error occurs
    log4net.ILog log = log4net.LogManager.GetLogger(typeof(object));
    using (log4net.NDC.Push(this.User.Identity.Name))
    
        log.Fatal("Unhandled Exception", Server.GetLastError());
    

不要太在意 log4net 的东西,Server.GetLastError() 是最有用的部分,记录你喜欢的细节。

【讨论】:

对我来说,我还必须在我的 Web.config 中将 customErrors 模式设置为 Off 。否则它仍然无法正常工作。不幸的是,我没有意识到这是必需的。有些事情告诉我,我需要更仔细地查找并开始阅读文档。【参考方案4】:

ELMAH project 听起来值得一试,它的功能列表包括:

ELMAH(错误记录模块和 处理程序)是应用程序范围的错误 完整的测井设施 可插拔。可以动态添加 到正在运行的 ASP.NET Web 应用程序, 甚至所有 ASP.NET Web 应用程序 在机器上,无需任何 重新编译或重新部署。

记录几乎所有未处理的异常。 用于远程查看重新编码异常的整个日志的网页。 一个网页可以远程查看任何登录的人的完整详细信息 例外。 在许多情况下,您可以查看原始的黄屏死机 为给定生成的 ASP.NET 异常,即使使用 customErrors 模式 已关闭。 每个错误发生时的电子邮件通知。 日志中最近 15 个错误的 RSS 提要。 许多日志的后备存储实现

dotnetslackers 中有关使用 ELMAH 的更多信息

【讨论】:

【参考方案5】:

您可以订阅AppDomain.CurrentDomain.UnhandledException 事件。

【讨论】:

从 2.00 开始,这不会捕获异常,只是让您有机会记录它。然而,它确实回到了 1.0 -> 1.1,但这是在 2.00 中修复的错误。我认为您还需要 Application.ThreadException 或类似的东西才能全面覆盖。 同意,但 OP 的意图似乎是记录和重定向,而不是陷阱。 Application.ThreadException 是针对 WinForms,而不是 WebForms。 我相信 Quarrelsome 是正确的,因为 OP 的问题是指 Windows 应用程序而不是 Web 应用程序。 嗯,“我有一个用 C#.NET 构建的网站”。 我说的确实是一个网络应用【参考方案6】:

请务必注意,您不应该捕获未处理的异常。如果您遇到 SQL 超时问题,您应该专门解决​​这些问题。

【讨论】:

您的论点“注意您不应该捕获未处理的异常可能很重要。”完全不合逻辑(没有参考资料、理由等)。另外,您听说过throw 声明吗? 我会提供更多信息。 Microsoft 不希望您捕获(异常)。您将无法通过 Windows 徽标认证。您也不应该抛出新异常,通常应该抛出 ApplicationException。你总是应该捕捉到最具体的有意义的异常。 捕获任何异常,记录它,然后抛出它是有用的。我认为人们能做的最糟糕的事情就是将他们所有的代码都包含在 try-catch 中……或者甚至更糟糕的基于 try-catch 的听写流。例如,我有一个程序,它有一个附加到 UnhandledException 的委托,所以每当有任何事情未处理时,我都会得到异常。然后我将其提交给 Web 服务,然后让我的软件崩溃。否则您将如何找出问题所在? 对不起,我应该完成我的想法。只要你重新扔它就可以了。捕获(异常 e) 日志(e); 不好。捕获(异常 e) 日志(e);扔; 很好。【参考方案7】:

您的意思是在所有线程中处理它,包括由第三方代码创建的线程?在“已知”线程中,只需在堆栈顶部捕获 Exception

【讨论】:

【参考方案8】:

我建议查看log4net,看看它是否适合问题的日志记录部分。

【讨论】:

【参考方案9】:

如果使用 .net 2.0 框架,我使用内置的健康监控服务。这里有一篇很好的文章描述了这种方法:http://aspnet.4guysfromrolla.com/articles/031407-1.aspx

如果您坚持使用 1.0 框架,我会使用 ELMAH: http://msdn.microsoft.com/en-us/library/aa479332.aspx

希望对你有帮助

【讨论】:

【参考方案10】:

这个问题处理和识别有 2 个部分。

识别

这是当异常最终被捕获时你所做的,不一定是它被抛出的地方。所以那个阶段的异常必须有足够的上下文信息让你确定问题所在

处理

对于处理,您可以 a) 添加一个 HttpModeule。看 http://www.eggheadcafe.com/articles/20060305.asp 仅当绝对没有可用的上下文信息并且可能存在与 IIS/aspnet 相关的问题时,我才会建议这种方法,简而言之,对于灾难性情况

b) 创建一个名为 AbstractBasePage 的抽象类,它派生自 Page 类,并让所有代码隐藏类都派生自 AbstractBasePage

AbstractBasePage 可以实现 Page.Error 委托,以便可以在此处捕获(并可能记录)通过 n 层架构向上渗透的所有异常

对于您正在谈论的异常类型 (SQlException),我会建议这种原因,因为您有足够的上下文信息来识别它是超时并采取可能的措施。此操作可能包括将用户重定向到自定义错误页面,并针对每种不同类型的异常(Sql、Web 服务、异步调用超时等)提供适当的消息。

谢谢 房车

【讨论】:

【参考方案11】:

一个简短的答案是在调用委托时使用(匿名)委托方法和公共处理代码。

背景:如果您已经针对弱点,或者有一些样板错误处理代码,您需要普遍适用于特定类别的问题,并且您不想编写相同的尝试..catch 用于每个调用位置,(例如更新每个页面上的特定控件等)。

案例研究:一个痛点是 Web 表单和将数据保存到数据库。我们有一个向用户显示保存状态的控件,我们希望在每个页面中都有通用的错误处理代码和通用显示,而不需要复制粘贴重用。此外,每个页面都以自己的方式做自己的事情,因此代码中唯一真正常见的部分是错误处理和显示。

现在,在被抨击之前,这不能替代数据访问层和数据访问代码。这一切仍然假设存在,良好的 n 层分离等。这段代码是特定于 UI 层的,允许我们编写干净的 UI 代码而不是重复自己。我们坚信不取消异常,但某些异常不应该让用户获得通用错误页面并丢失他们的工作。会出现sql超时、服务器宕机、死锁等情况。

一种解决方案:我们这样做的方式是将匿名委托传递给自定义控件上的方法,并实质上使用匿名委托注入 try 块。

// normal form code.
private void Save()

    // you can do stuff before and after. normal scoping rules apply
    saveControl.InvokeSave(
        delegate
        
            // everywhere the save control is used, this code is different
            // but the class of errors and the stage we are catching them at
            // is the same
            DataContext.SomeStoredProcedure();
            DataContext.SomeOtherStoredProcedure();
            DataContext.SubmitChanges();
        );

SaveControl 本身有如下方法:

public delegate void SaveControlDelegate();

public void InvokeSave(SaveControlDelegate saveControlDelegate)
        
    // I've changed the code from our code. 
    // You'll have to make up your own logic.
    // this just gives an idea of common handling.
    retryButton.Visible = false;
    try
    
        saveControlDelegate.Invoke();
    
    catch (SqlTimeoutException ex)
    
        // perform other logic here.
        statusLabel.Text = "The server took too long to respond.";
        retryButton.Visible = true;
        LogSqlTimeoutOnSave(ex);
    
    // catch other exceptions as necessary. i.e.
    // detect deadlocks
    catch (Exception ex)
    
        statusLabel.Text = "An unknown Error occurred";
        LogGenericExceptionOnSave(ex);
    
    SetSavedStatus();


还有其他方法可以实现这一点(例如,通用基类、接口),但在我们的例子中,这是最合适的。 这不能替代Elmah 等用于记录所有未处理异常的出色工具。这是一种以标准方式处理某些异常的有针对性的方法。

【讨论】:

【参考方案12】:

如果您没有强制关闭 sqlconnections,通常会发生超时错误。

所以如果你有一个

try 
    conn.Open();
    cmd.ExecuteReader();
    conn.Close();
 catch (SqlException ex) 
    //do whatever

如果该 ExecuteReader 出现任何问题,您的连接将不会关闭。总是添加一个 finally 块。

try 
    conn.Open();
    cmd.ExecuteReader();
    conn.Close();
 catch (SqlException ex) 
    //do whatever
 finally 
    if(conn.State != ConnectionState.Closed)
       conn.Close();

【讨论】:

-1:你迟到了四个八个月,而且你没有实施using 块,这是避免你所说的问题的正确方法。 最终没有使用块做同样的事情吗? btw 来自 C# 参考:using 语句可确保调用 Dispose,即使在调用对象上的方法时发生异常也是如此。您可以通过将对象放在 try 块中,然后在 finally 块中调用 Dispose 来获得相同的结果;事实上,编译器就是这样翻译 using 语句的。 你可以达到同样的效果,但是下一个出现的人会搞砸。每当您实例化实现 IDisposable 的类时,始终使用 using 块是防止这些资源泄漏的最佳方法。在这样的网站上更重要的是,经验不足的开发人员可能会从我们这些应该更了解的人那里复制并粘贴代码“示例”。让我知道您是否希望我清理您的,或者创建一个单独的答案,干净,但参考您的。 虽然@John Saunders 关于使用块是正确的,但如果您想处理异常(即使它只是用于记录),您仍然需要将 cmd.ExecuteReader 包装在 try..catch 中。【参考方案13】:

这是一个老问题,但最好的方法(对我来说)没有在这里列出。所以我们在这里:

ExceptionFilterAttribute 对我来说是一个不错且简单的解决方案。来源:http://weblogs.asp.net/fredriknormen/asp-net-web-api-exception-handling。

public class ExceptionHandlingAttribute : ExceptionFilterAttribute

    public override void OnException(HttpActionExecutedContext context)
    
        var exception = context.Exception;
        if(exception is SqlTimeoutException)
        
            //do some handling for this type of exception
        
    

并将其附加到 f.e.主控制器:

[ExceptionHandling]
public class HomeController: Controller



【讨论】:

以上是关于在 C#.NET 中捕获所有未处理异常的简单方法的主要内容,如果未能解决你的问题,请参考以下文章

iOS异常信号的捕获和简单处理

捕获块未捕获异常

在 Android 中设置全局未捕获异常处理程序的理想方法

在 ASP.NET 应用程序中捕获所有异常的最佳方法是啥?

.net捕获全局异常并且记录日志多线程方式发送邮件提醒

Android_程序未处理异常的捕获与处理