【问题标题】:try/catch vs AppDomain.UnhandledException to log & crash the applicationtry/catch vs AppDomain.UnhandledException 来记录和崩溃应用程序
【发布时间】:2013-01-15 16:28:04
【问题描述】:

据我所知,您应该只在实际处理异常时才使用 try/catch,而不仅仅是报告和记录它然后使应用程序崩溃。否则,您最好只检查有意义的不同场景(例如,如果 sth==null)或者 - 如果您的目的只是记录异常并使应用程序崩溃 - 使用 AppDomain.UnhandledException。但这种情况总是如此吗?为什么?

假设以下方法,它接受一个数组并在执行一些数据库和文件系统操作后返回 MemoryStream。

MemoryStream Read (int[] IDs)
{
    try
    {
        using (SqlConnection connection = new SqlConnection(connection_string))
        {
            connection.Open();    

                        // a bunch of code executing SQL queries & constructing MemoryStream, which is returned at the end of the block
        }
    }
    catch (Exception e)
    {
        // report the exception to the user & log it
        throw; // pointles??                
    }
}

有多种情况可以被视为异常/不需要的行为,例如:

  • 参数 (IDs[]) 为空,
  • 未能建立 SQL 连接,
  • 未能执行特定的 SQL 查询。

所有这些情况都被认为是异常的,如果您只想记录异常(然后崩溃),仍然将所有内容放在 try/catch 中可能是不好的做法 - 但为什么呢?在上述情况下,最好的处理行为是什么?完全避免 try/catch,使用 if 语句检查 null 引用(在这种情况下返回 null)并使用 AppDomain.UnhandledException 记录其他所有内容?使用 try/catch,但仍然使用 if 语句检查空引用(并在这种情况下返回)?还有什么?

【问题讨论】:

  • 即使您觉得我的回答没有用,我建议您阅读我提供的链接。我相信您会发现它们非常宝贵!
  • @alan,感谢您的回答,我确实觉得它很有用,并且正在阅读链接。 =)

标签: c# .net exception-handling try-catch


【解决方案1】:

使用 try/catch 语句来添加代码只是为了使应用程序崩溃是没有效率的。 CLR 已经为您解决了这个问题。并且您有 AppDomain.UnhandledException 来生成体面的信息来诊断原因。

只有在您必须清理某些东西的非常特殊的情况下,比如说一个您不想继续放置的文件,您才应该考虑编写一个 try/catch。这本身就是一个非常不确定的要求,无法保证您的 catch 块将执行。当异常像 StackOverflowException 或 ExecutionEngineException 这样令人讨厌时,它不会。或者更常见的原因是程序无法自行清理,有人绊倒电源线或从任务管理器中终止进程。

【讨论】:

  • 捕捉到您不打算在之后清理的异常的原因有很多。一种是格式化异常并返回用户友好的内容,或者防止敏感的堆栈跟踪信息被广播到世界各地。此外,在您的 catch 块示例中,您不会使用 catch 来清理文件,而是使用 finally 块;即使发生异常,也始终保证执行。
  • 确切地说,据我所知,finally{} 实际上并不能保证总是执行 - 例如,StackOverflowException 或电源故障不会导致其执行。
  • 在标准异常的情况下(这是您期望记录/处理的所有内容),它保证执行。我认为考虑堆栈溢出或电源故障可能有点过分。
【解决方案2】:

您应该只在实际处理异常时才使用 try/catch,而不仅仅是报告和记录它然后使应用程序崩溃

我同意第一部分,但我要补充一点,当您可能无法控制调用层时,在层边界添加日志记录是很有价值的。例如我在顶部方法中记录 Web 服务中发生的所有异常,以确保我已登录服务器,因为调试信息(堆栈跟踪等)并不总是优雅地跨通信层

在您的特定示例中,我会检查“异常”条件在哪里可以,但让其他异常“自然”发生。对于您的具体示例:

  • 参数 (IDs[]) 为空,
  • 未能建立 SQL 连接,
  • 未能执行特定的 SQL 查询。

对于第一个,我会检查 null 参数,原因有一个:NullReferenceException 给你no关于异常原因的上下文,除了 where em> 它发生了。我更喜欢检查null,然后抛出一个新的ArgumentNullException 异常,因为您可以添加 which 参数为空。您可能仍需要进行一些挖掘以找出为什么它为空,但它可以为您节省大量调试时间。

SQL 异常通常会自然而然地冒出,因为其中包含不错的错误信息(例如 "undeclared variable '@arg'")

【讨论】:

    【解决方案3】:

    我最近开始自己阅读这个主题。我的基本理解是:

    1. 仅当您计划处理异常时才捕获异常。
    2. 过度使用 try/catch 会导致异常吞咽和/或丢失有价值的堆栈跟踪信息,并可能导致可维护性问题(如果您决定将错误/日志标准化怎么办?)。而是使用 try/finally 或使用块来实现清理。
    3. 通过全局异常处理程序在边界处捕获异常。
    4. 使用 AppDomain.UnhandledException 来准确理解名称的含义:记录未处理的异常。如果你不记录这些,你只会在日志查看器中找到一个 CLR“Windows 错误报告”条目和一些对你真的没用的转储文件。使用 AppDomain.UnhandledException 总是一个好主意,因此如果您的应用程序确实崩溃了,您知道原因。

    请务必注意,“处理”异常并不一定意味着清理或追溯逻辑。处理可能只是意味着将错误格式化为对用户更友好的内容,或者隐藏您不希望任何人看到的敏感堆栈跟踪。我通常会记录详细的错误并返回格式化的错误。

    再说一次,这正是我最初收集到的。以下是一些来源:

    Good Exception Management Rules of Thumb

    Understanding and Using Exceptions

    【讨论】:

    • 很好的链接,谢谢。如果您只想以一致的方式报告和记录异常,那么实现全局异常处理程序似乎是一个不错的选择 - 这里提供了一个干净的示例实现:link
    猜你喜欢
    • 2019-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-01
    • 1970-01-01
    相关资源
    最近更新 更多