【问题标题】:Try..Catch blocks always expensive? [duplicate]Try..Catch 块总是很贵? [复制]
【发布时间】:2011-01-05 17:14:38
【问题描述】:

可能重复:
Do try/catch blocks hurt performance when exceptions are not thrown?

大家好, 只是一个关于 try..catch 块的快速问题。我听说它们使用起来很昂贵,不应该用作程序流程的一部分。但是,为了验证电子邮件地址,我使用了以下代码。

        try
        {
            MailAddress checkEmail = new MailAddress(testEmail);

            return true;
        }
        catch
        {
            return false;
        }

由于之前的验证,我不会捕获很多异常,除非它是试图绕过验证。我的问题是,Try...Catch 块是否仅在捕获异常时才昂贵,或者无论是否抛出任何异常,它都总是昂贵?

谢谢

编辑:感谢所有回复。我已经决定,因为检查(在 C# 中)不是很昂贵,所以我会坚持使用这种方法。这主要是因为很少发生实际抛出的异常,因为之前的验证步骤可确保没有人意外输入无效的电子邮件地址。

【问题讨论】:

  • 你说异常不应该发生,如果发生了,那是有人试图绕过你的安全机制的结果。既然您知道系统中存在攻击者,那么吃掉异常并愉快地继续前进似乎是明智的吗?我会让异常冒泡到调用者并停止程序。当您注意到有一个持斧头的敌对人绕过了您的建筑物安全并触发了警报时,您让警报响起并召唤安全;你不会自动关闭闹钟!
  • 嗨,埃里克。我的实际代码比这大得多。它监视用户、记录数据并阻止访问。我只是展示了一个我正在做的示例调用,看看它是否是一种有效的做事方式:)
  • 另请参阅此问题:stackoverflow.com/questions/1308432/…
  • @Eric - 精彩的引述 - 让我微笑,谢谢。
  • 记住@Skider“安全是一种幻觉”

标签: c# performance exception try-catch


【解决方案1】:

一般,在今天的实现中,输入try 块一点也不昂贵(这并不总是正确的)。然而,抛出和处理一个异常通常是一个相对昂贵的操作。因此,异常通常应该用于异常事件,而不是正常的流控制。

不过,性能只是需要考虑的一个因素,尤其是在现代世界中。如果(例如)您为响应用户操作而做某事,那么从性能的角度来看,您是否使用异常可能并不重要,即使您可以进行主动检查,只要异常发生得足够快用户不会感到震惊。¹但是,如果您正在一个紧密的循环中执行将要运行数十万次的操作,或者您正在编写一个可能需要处理巨大负载的 Web 应用程序,那么您可能会希望避免在正常情况下使用异常。


¹ 十多年前,我负责对 .Net 1.1“无接触部署”应用程序进行增强,在该应用程序中引发的第一个异常需要整整三秒钟。在一个涉及打开用户要求的文件的用例中,这是一个足够的问题,该文件可能合理地不存在,我必须在尝试打开文件之前添加文件存在检查,这通常是糟糕的编程实践(只是如果失败,请尝试打开文件并处理异常),纯粹是因为等待构建该异常的用户体验太差了。但这可能不是我们现在生活的世界。

【讨论】:

  • 嗨,对不起,我以为我标记了 C#。我现在已经修好了。我只希望在用户故意篡改 POST 标头时抛出异常,因为标准验证应用于输入。
  • 很好地回答@T.J.我还要补充一下处理异常时对性能的影响,不要忘记此时应用程序已经停止按预期运行(即谚语已经引起了粉丝的注意),这表明需要解决一个更大的问题,因此此时性能可能并不是一个大问题。这当然遵循 T.J. 的“异常只应用于异常事件”的观点——我仍然发现许多开发人员并不欣赏这一点。
  • 从什么时候开始打开文件是一项性能关键的操作?恕我直言,try-catch 在这种情况下更具可读性,与操作的整体复杂性相比,性能差异可以忽略不计。
  • @dsimcha: 很好,虽然如果你发现简单的 if 语句难以阅读...... ;-) 我使用过会导致异常(给定类型的第一个异常)的框架2-3 秒的停顿导致用户体验下降;打开本地文件是一个惊人的亚秒级操作。
  • @dsimcha:那个特定的例子是一个 .Net 1.1 “富 Web 客户端”;谢天谢地,我们终于让那个客户升级到了更新的东西。通过 NTD 运行和作为本地可执行文件运行时都会发生。但撇开这一点不谈,对于用户提供的路径,我肯定会在打开之前检查(而如果代码应该能够期望它在那里,我不知道),因为正常情况之一是文件不存在;否则,维护编码人员必须向下滚动并指向您的异常处理代码,以找出您为这种(在我看来)非异常情况所做的工作。
【解决方案2】:

除非有例外,否则它们非常便宜。因此,当预期出现异常时,您应该避免使用它们,如上面的示例所示。

我认为错误的用户输入异常通常是不明智的。内存不足或其他意外故障的异常很好。

【讨论】:

  • 输入通常使用正则表达式进行验证。但是,如果有人故意绕过验证(通过篡改 POST 标头),则可能会抛出此异常。
  • 这是一个判断电话。但我会说这已经足够特殊了。
【解决方案3】:

只是为了扮演魔鬼的拥护者 - 我发现使用异常进行流量控制的一个很好的用途:“取消”按钮。

如果您在某些服务上运行可能需要 10-120 分钟的功能,该功能正在做很多不同的事情,我发现这样做 if(hasCanceled) 会在我的 Log() 中抛出新的 JobCancelledException()功能(在我所处的每个步骤中记录)工作起来非常棒。它退出当前的代码执行并停止运行该作业——这正是我所需要的。我确信有一些更好的方法可以做到这一点,也许以某种方式使用事件 - 但就我而言,它效果很好(特别是因为工作不会定期取消)。

除此之外 - 我 100% 同意永远不应将异常用作流控制工具。

@Kornel - 我对那个帖子有两个词......神圣的$hit =)

这是一个简单的伪代码示例:

Class Job
{
       Public Run(param1,param2,etc...)
       {
             Try
            {
                   Log("Doing Something")
                   DoSomething()

                         Log("Doing Another")
                         DoAnother()

                         Log("This keeps going, etc, inside of these function we make the same Log calls where it makes sense")
                         Etc()
                   }
                   Catch(JobCancelledException)
                   {
                        status="Cancelled"
                   }
            }

              Private Log(ByVal str As String)
              {
                      MessateToUser(str)

                      if(hasCancelled)
                          throw new JobCancelledException
            }

             private SomeEvent_WhenUserPushesCancelButton()
             {
                       hasCancelled=True
             }
}

【讨论】:

  • 这是一种实现用户可以执行的某些作业的“取消”按钮/功能的方法。给定正确的参数,此作业可能需要数小时才能运行。无论如何,我在每个不同的步骤都记录()以提供用户反馈。所以每次我登录时,我都会检查一个 hasCancelled 字段,如果它是真的,我会抛出一个 JobCancelledException。现在,无论我们在代码中的哪个位置,作业都会立即停止,因为我在调用堆栈的最高级别处理此异常。超级简单,效果很好。
【解决方案4】:

只有在抛出异常时,异常才是昂贵的。我确信设置一个 Try..Catch 块的成本非常低,但它远远超过了根本不捕获异常和让程序崩溃的成本。正如其他人指出的那样,Try..Catch 块应该只用于特殊情况。

【讨论】:

    【解决方案5】:

    try 块的开销非常低,所以如果没有抛出异常,那么应该没有明显的惩罚。抛出异常时发生的主要开销是查找处理程序时发生的堆栈遍历 - 因为您将异常缓存得离源非常近,所以我怀疑会有很多性能问题。理想情况下,虽然您可以事先正确验证您的输入,但电子邮件验证相当复杂,因此在这种情况下可能不值得。

    【讨论】:

      【解决方案6】:

      只有抛出异常才代价高昂,但这不是使用异常作为正常流控制的借口。

      如果您可以预先验证某些内容以避免一开始就发生异常,那么就这样做。例如。而不是您发布的内容,最好是这样的:

      string invalidAddress = "notvalid@@@@@@lolzors.bomb";
      return MailAddressValidator.IsValid(invalidAddress ); // doesn't cause exception
      

      该规则的例外是当您必须滚动您自己版本的复杂方法(例如,在没有TryParse 方法的基类库中找到的东西)以避免异常时,从大局来看,无所谓

      如果您不确定,请使用代表性数据进行分析。如果是用户每隔一秒在客户端应用程序表单中输入数据,那没关系。但是,如果它是一项用于处理可能来自任何地方的数千个电子邮件地址的服务,那么您应该确定自己的假设。

      【讨论】:

        【解决方案7】:

        try..catch 块永远不应用作程序流控制的工具。

        如果您不相信,请阅读this thread

        【讨论】:

        • 从来没有?永远不能?就算你深深明白是怎么回事?如果像目前的情况一样,这是一种简单、有效且无错误的方法来做一些原本很难做的事情? (-1 表示没有努力将当前案例与一般原则区分开来)。
        • @Boris,尤其是在 100 万次迭代的循环中运行时。
        【解决方案8】:

        一般来说,现代编译器只对try 块施加最低成本,除非抛出异常。它们仍然不应该用于程序流控制,因为它们不像标准流构造那样明显。异常本质上等同于COME FROM 语句。

        【讨论】:

          【解决方案9】:

          虽然您确实不应该使用 try..catch 进行程序流控制,但如果代码中实际上没有抛出异常,我不知道有任何性能问题

          【讨论】:

            【解决方案10】:

            这与语言有关,但据我所知,在 Java 中,如果没有引发异常,则使用 try/catch 几乎没有性能成本。

            因此,在您使用预先验证的电子邮件地址的示例中,您所拥有的一切都很好。

            【讨论】:

            • 谢谢。我正在使用 C#(我已经修复了标签)。我认为它在 C# 中与在 Java 中是相似的。我使用预先验证的电子邮件地址来确保完整性,但无论如何都会调用它。
            猜你喜欢
            • 2015-03-27
            • 1970-01-01
            • 2013-05-03
            • 2012-02-18
            • 2012-11-25
            • 1970-01-01
            • 1970-01-01
            • 2016-05-21
            相关资源
            最近更新 更多