C#中Try-Catch语句真的影响程序性能吗?
很多帖子都分析过try-catch的机制,以及其对性能的影响。
但是并没有证据证明,try-catch过于损耗了系统的性能,尤其是在托管环境下。记得园子里有位网友使用stopwatch分析过try-catch在不同情况下,与无try-catch的代码相比,代码运行的时间指标,结果并没有很大差异。
下面我来结合il分析一下try-catch吧。
● 机制分析
.net 中基本的异常捕获与处理机制是由try…catch…finally块来完成的,它们分别完成了异常的监测、捕获与处理工作。一个try块可以对应零个或多个catch块,可以对应零个或一个finally块。不过没有catch的try似乎没有什么意义,如果try对应了多个catch,那么监测到异常后,clr会自上而下搜索catch块的代码,并通过异常过滤器筛选对应的异常,如果没有找到,那么clr将沿着调用堆栈,向更高层搜索匹配的异常,如果已到堆栈顶部依然没有找到对应的异常,就会抛出未处理的异常了,这时catch块中的代码并不会被执行。所以距离try最近的catch块将最先被遍历到。
如有以下代码:
try
{
convert.toint32("try");
}
catch (formatexception ex1)
{
string catchformatexception = "catchformatexception";
}
catch (nullreferenceexception ex2)
{
string catchnullreferenceexception = "catchnullreferenceexception";
}
finally
{
string finally = "finally";
}
对应il如下:
.method private hidebysig instance void form1_load(object sender,
class [mscorlib]system.eventargs e) cil managed
{
// code size 53 (0x35)
.maxstack 1
.locals init ([0] class [mscorlib]system.formatexception ex1,
[1] string catchformatexception,
[2] class [mscorlib]system.nullreferenceexception ex2,
[3] string catchnullreferenceexception,
[4] string finally)
il_0000: nop
il_0001: nop
il_0002: ldstr "try"
il_0007: call int32 [mscorlib]system.convert::toint32(string)
il_000c: pop
il_000d: nop
il_000e: leave.s il_0026
il_0010: stloc.0
il_0011: nop
il_0012: ldstr "catchformatexception"
il_0017: stloc.1
il_0018: nop
il_0019: leave.s il_0026
il_001b: stloc.2
il_001c: nop
il_001d: ldstr "catchnullreferenceexception"
il_0022: stloc.3
il_0023: nop
il_0024: leave.s il_0026
il_0026: nop
il_0027: leave.s il_0033
il_0029: nop
il_002a: ldstr "finally"
il_002f: stloc.s finally
il_0031: nop
il_0032: endfinally
il_0033: nop
il_0034: ret
il_0035:
// exception count 3
.try il_0001 to il_0010 catch [mscorlib]system.formatexception handler il_0010 to il_001b
.try il_0001 to il_0010 catch [mscorlib]system.nullreferenceexception handler il_001b to il_0026
.try il_0001 to il_0029 finally handler il_0029 to il_0033
} // end of method form1::form1_load
末尾的几行代码揭示出il是怎样处理异常处理的。最后三行的每一个item被称作exception handing clause,ehc组成exception handing table,eht与正常代码之间由ret返回指令隔开。
可以看出,formatexception排列在eht的第一位。
当代码成功执行或反之而返回后,clr会遍历eht:
1. 如果抛出异常, clr会根据抛出异常的代码的“地址”找到对应的ehc(il_0001 to il_0010为检测代码的范围),这个例子中clr将找到2条ehc,formatexception会最先被遍历到,且为适合的ehc。
2. 如果返回的代码地址在il_0001 to il_0029内,那么还会执行finally handler 即il_0029 to il_0033中的代码,不管是否因成功执行代码而返回。
事实上,catch与finally的遍历工作是分开进行的,如上文所言,clr首先做的是遍历catch,当找到合适的catch块后,再遍历与之对应finally;而且这个过程会递归进行至少两次,因为编译器将c#的try…catch…finally翻译成il中的两层嵌套。
当然如果没有找到对应的catch块,那么clr会直接执行finally,然后立即中断所有线程。finally块中的代码肯定会被执行,无论try是否检测到了异常。
改进建议
由上面的内容可以得出:
如果使用了“try-catch”,且捕获到了异常,clr做的只不过是遍历exception handing table中的catch项;然后再次遍历exception handing table中的finally项,所用时间几乎都花费在遍历exception handing table上;而如果没有捕获到异常,clr只是遍历exception handing table中的finally项,所需时间微乎其微。
而“try-catch”遍历后的执行对应操作所用时间,则根据你的具体代码所定,“try-catch”引起的只是监控与触发,不应将这部分的代码时间也算“try-catch”的消耗。
所以,可以从性能和代码评审两方面考虑,一般建议有以下几点准则:
1.尽量给clr一个明确的异常信息,不要使用exception去过滤异常
2.尽量不要将try…catch写在循环中
3. try尽量少的代码,如果有必要可以使用多个catch块,并且将最有可能抛出的异常类型,书写在距离try最近的位置
4.不要只声明一个exception对象,而不去处理它。这样做白白增加了exception handing table的长度。
5.使用性能计数器实用工具的“clr exceptions”检测异常情况,并适当优化
6.使用成员的try-parse模式,如果抛出异常,那么用false代替它
结论,try-catch虽然会消费一点时间,但程序人员大可不必谈虎色变,通过上面的分析,与其说“try-catch”会损耗或影响性能,不如说“try-catch”与其他代码一样,只是性能的普通消费者,但出于代码书写评审方面的考虑,还是尽量关照一下“try-catch”吧。
上一篇: 依赖注入(Dependency Injection)
下一篇: C#多线程的同步与通信