在C#下,多少性能命中是try,throw和catch块

时间:2009-03-05 18:26:00

标签: c# performance catch-block

首先,免责声明: 我有其他语言的经验,但我仍在学习C#的微妙之处

关于问题...我正在查看一些代码,它以一种与我有关的方式使用try / catch块。当调用解析例程而不是返回错误代码时,程序员使用以下逻辑

catch (TclException e) {
  throw new TclRuntimeError("unexpected TclException: " + e.Message,e);
}  

这是由调用者捕获的,它会抛出相同的错误...
...被调用者捕获,抛出相同的错误...
.....被调用者捕获,这会引发相同的错误......

备份大约6个级别。

我认为所有这些catch / throw块都会导致性能问题,或者这是C#下的合理实现吗?

8 个答案:

答案 0 :(得分:14)

在任何语言下这都是一个糟糕的设计。

异常旨在捕获您可以处理它们的级别。捕获异常,只是再次抛出异常只是浪费时间(这也会导致您丢失有关原始错误位置的有价值信息)。

显然,编写该代码的人曾经使用过错误代码,然后切换到异常,却没有真正了解它们的工作原理。如果在一个级别没有捕获,则异常会自动“冒泡”堆栈。

另请注意,例外出现的例外情况。应该从不发生的事情。它们不应该用于正常的有效性检查(即,不要捕获除零异常;检查预先除数是否为零)。

答案 1 :(得分:14)

根据msdn:Performance Tips and Tricks,你可以使用try and catch而不会出现任何性能问题直到真正的抛出

答案 2 :(得分:10)

投掷(而不是捕获)是昂贵的。

除非你打算做一些有用的事情(即转换为更有用的异常,处理错误),否则不要设置catch块。

只需重新抛出异常(不带参数的throw语句),或者更糟糕的是,抛出刚捕获的同一个对象绝对是错误的。

编辑:为避免含糊不清:

重新抛出:

catch (SomeException) {
  throw;
}

从上一个异常对象创建异常,其中覆盖了所有运行时提供的状态(特别是堆栈跟踪):

catch (SomeException e) {
  throw e;
}

后一种情况是抛弃有关异常的信息的毫无意义的方法。并且没有任何东西在捕获区块中投掷之前是毫无意义的。情况可能更糟:

catch (SomeException e) {
  throw new SomeException(e.Message);
}

几乎丢失所有包含的有用状态信息(包括最初抛出的内容)。

答案 3 :(得分:2)

通常,抛出异常在.NET中代价很高。只需要一个try / catch / finally块就不行了。所以,是的,从性能角度来看,现有的代码是糟糕的,因为当它抛出时,它会抛出5-6个膨胀的异常,而不会添加任何值而只是让原始异常自然地冒出5-6个堆栈帧。

更糟糕的是,从设计角度来看,现有代码确实很糟糕。异常处理的主要好处之一(与返回错误代码相比)是您无需在任何地方(在调用堆栈中)检查异常/返回代码。您只需要在实际想要处理它们的少数几个地方捕获它们。忽略异常(与忽略返回代码不同)不会忽略或隐藏问题。它只是意味着它将在调用堆栈中处理得更高。

答案 4 :(得分:2)

这本身并不可怕,但人们应该真正关注嵌套。

可接受的用例是这样的:

我是一个低级组件,可能会遇到许多不同的错误,但我的消费者只对特定类型的异常感兴趣。因此我可以这样做:

catch(IOException ex)
{
    throw new PlatformException("some additional context", ex);
}

现在这允许消费者做:

try
{
    component.TryThing();
}
catch(PlatformException ex)
{
   // handle error
}

是的,我知道有些人会说,但消费者应该捕获IOException,但这取决于消费代码的实际抽象程度。如果Impl将某些内容保存到磁盘并且消费者没有正当理由认为他们的操作会触及磁盘怎么办?在这种情况下,将此异常处理放在使用代码中是没有意义的。

我们通常试图通过使用这种模式来避免在业务逻辑代码中放置一个“全能”异常处理程序,因为我们想要找出所有可能的异常类型,因为它们可能会导致更基本的问题,需要调查。 如果我们没有捕获,它会冒泡,点击“顶级”级别的处理程序,而应该停止应用程序继续前进。这意味着客户报告该异常,您将有机会对其进行调查。当您尝试构建健壮的软件时,这很重要。您需要找到所有这些错误情况并编写特定代码来处理它们。

什么不是非常漂亮的是嵌套这些过多的金额,这是您应该使用此代码解决的问题。

正如另一张海报所说,例外情况属于合理例外的行为,但不要太过分。基本上代码应表示“正常”操作,异常应处理您可能遇到的潜在问题。

就性能而言,异常是好的,如果你使用调试器对嵌入式设备进行测试,那么你会得到可怕的结果,但是在没有调试器的情况下,它们实际上非常快。

在讨论异常性能时,人们忘记的主要问题是,在错误情况下,一切都会因为用户遇到问题而减慢。当网络瘫痪且用户无法保存工作时,我们真的关心速度吗?我非常怀疑将错误报告更快地返回给用户几毫秒就会产生影响。

在讨论异常时要记住的主要原则是异常不应出现在正常的应用程序流程中(正常意味着没有错误)。其他一切都源于这种说法。

在确切的例子中,你给我的不确定。在我看来,在另一个通用的声音tcl异常中包装看似普通的tcl异常并没有带来什么好处。如果有什么我建议跟踪代码的原始创建者并了解他的思想背后是否有任何特定的逻辑。有可能你可以杀掉捕获物。

答案 5 :(得分:1)

尝试/捕捉/投掷很慢 - 更好的实施方法是在捕获之前检查该值,但如果你绝对无法继续,那么你最好只在重要时投掷和捕获。否则,检查和记录会更有效。

答案 6 :(得分:0)

如果堆栈的每一层都重新使用相同的信息重新抛出相同的类型,则不添加任何新内容,那么这完全是荒谬的。

如果它发生在独立开发的图书馆之间的边界,那是可以理解的。有时,库作者想要控制哪些异常从其库中逃脱,以便他们可以在以后更改其实现,而无需弄清楚如何模拟先前版本的异常抛出行为。

在没有充分理由的情况下,在任何情况下捕捉和重新投掷通常都是一个坏主意。这是因为只要找到catch块,就会执行throw和catch之间的所有finally块。只有在可以从中恢复异常时才会发生这种情况。在这种情况下没关系,因为正在捕获特定类型,因此代码的作者(希望)知道他们可以安全地撤消任何自己的内部状态更改以响应该特定异常类型。

因此,那些try / catch块在设计时可能会产生成本 - 它们会使程序更加混乱。但是在运行时,它们只会在抛出异常时产生很大的成本,因为异常向上传输已经变得更加复杂。

答案 7 :(得分:-1)

例外情况很慢,尽量不要使用它们。

请参阅我给出的答案here

基本上,Chris Brumme(曾在CLR团队中)表示他们是作为SEH例外实现的,因此当他们被抛出时你会受到重创,不得不承受因操作系统堆栈而受到的惩罚。它是一个真正的excellent article,并深入探讨了抛出异常时会发生什么。例如:


  当然,最大的成本   例外是你实际投掷的时候   一。我会在接近尾声的时候回到这里   博客。


  

性能。例外是直接的   实际投掷和捕获时的成本   一个例外。他们也可能有   与推动相关的间接成本   方法入口处理程序。和他们   通常可以有一个潜在的成本   限制代码机会。


  

然而,有一个严重的长期   异常的性能问题   这必须考虑到你的   决定。

     

考虑一些事情   抛出异常时会发生:

     
      
  • 通过解释由此发出的元数据来获取堆栈跟踪   编译器引导我们的堆栈展开。

  •   
  • 在堆栈中运行一系列处理程序,调用每个处理程序   两次。

  •   
  • 补偿SEH,C ++和托管之间的不匹配   异常。

  •   
  • 分配托管的Exception实例并运行其构造函数。   最有可能的是,这需要查找   各种错误的资源   消息。

  •   
  • 可能需要浏览一下OS内核。经常拿硬件   异常。

  •   
  • 通知任何附加的调试器,分析器,向量异常处理程序   和其他有关方面。

  •   
     

离光年远   从函数返回-1   呼叫。例外是固有的   非本地的,如果有明显的话   和今天的持久趋势   架构,你必须这样做   保持本地化以获得良好的表现。

有些人会声称异常不是问题,没有性能问题,通常是好事。这些人得到了很多民众的选票,但他们完全错了。我见过微软的工作人员提出了同样的要求(通常是通常用于营销部门的技术知识),但马口的底线是谨慎使用它们。

古老的格言异常只应用于特殊情况,对于C#和其他任何语言都是如此。