zoukankan      html  css  js  c++  java
  • .Net Discovery系列之深入理解平台机制与性“.NET技术”能影响(下) 狼人:

      三.关于异常捕获机制

      虽然我们已经很辛苦了,但是仍然有很多原因使代码运行失败,如引用null引用、索引越界、内存溢出、类型转换失败等等。这就需要我们的代码有足够的容错能力,在代码运行失败时,及时、主动的处理这些异常。

      ● 机制分析

      .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是否检测到了异常。

      ● 性能影响与改进建议

      异常捕获与处理是有性能代价的,虽然这种代价在托管环境中度量起来比较困难,但是这个过程毕竟经过一系列的遍历。所以仅从性能方面考虑,一般建议有以下几点准则:

      1.尽量给CLR一个明确的异常信息,不要使用Exception去过滤异常

      2.尽量不要将try…catch写在循环中

      3. try尽量少的代码,如果有必要可以使用多个catch块,并且将最有可能抛出的异常类型,书写在距离try最近的位置

      4.不要只声明一个Exception对象,而不去处理它

      5.使用性能计数器实用工具的CLR Exceptions检测异常情况,并适当优化

      6.使用成员的Try-Parse模式,如果抛出异常,那么用false代替它

      四.关于字符串拼接

      ● 机制分析

      .Net字符串型的变量有一个很特殊的机制,这个机制叫做字符串的驻留,其变现为字符串恒定不可改变。

      简单的说,字符串一旦建立,就会永久驻留在内存中,当你修改这个字符串变量时,CLR会在内存中新建一个新值,并不会修改旧值,旧值只有被垃圾回收器回收后,那部分被占用的空间才会释放掉。

      这样设计的目的无疑是为了提高字符串型变量的建立,因为新建字符串型变量时,CLR首先做的是在驻留池中遍历是否有相同的值的字符串,如果有则直接挂接变量指针,否则才会新建,但是在某些情况下,性能却反而降低。

      ● 性能影响与改进建议

      下面通过例子简单的说一下字符串驻留机制,假设有如下代码:

    string str = ""
    string a = "str_1" + str;
    a
    = "str_2"+ str;

      第三行C#代码(a = "str_2"+ str;)的样子看起来是在修改变量a的旧值"str_1",但实际上是创建了一个新的字符串"str_2",然后将变量a的指针指向了"str_2"的内存地址,而"str_1"依然在内存中没有受到任何影响 ---这就是字符串的驻留,如果下一次有变量b的值被赋值为str_1,CLR不会重新为这个变量重新分配内存,而是直接将该变量的指针指向str_1,这样就提高了该变量的初始化速度,但是如果没有这样的一个b变量,那么str_1就会一直占用内存,直至垃圾收集,这样做浪费了内存资源。

      同样ToUpper、SubString、Trim、Replace、加号连接等操作都会产生驻留的字符串,各位在设计程序时要特别注意。

      经常看到有的同学使用Replace替换一个网页整个HTML的某些关键字,其实这样会极大的浪费内存,给垃圾回收器的策略引擎以错误的信号,使其频繁启动,从而导致性能的降低。

      所以,有以下建议供大家参考:

      1.在用Replace做大量字符串操作时,最好仅仅对最小单元进行操作

      2在尽量少的字符串中替换,有必要时还要配合正则的使用,在替换完毕后最好根据上下文的代龄情况,手动调用一次GC的回收方法;

      3.对大规模的字符串拼接操作,则推荐使用StringBuilder

      4.能用常量赋值的就别用变量。因为常量赋值的字符串是在编译器生成在字符串常量池的,关于常量池请参考Aicken以前的文章。

      相关文章:.Net Discovery系列-深入理解平台机制与性能影响(上)

                          .Net Discovery系列之-深入理解平台机制与性能影响 (中)

  • 相关阅读:
    宿主机( win 7 系统) ping 虚拟机VMware( cent os 6.6 ) 出现“请求超时”或者“无法访问目标主机”的解决方法
    Java实现 LeetCode 23 合并K个排序链表
    Java实现 LeetCode 23 合并K个排序链表
    Java实现 LeetCode 23 合并K个排序链表
    Java实现 LeetCode 22 括号生成
    Java实现 LeetCode 22 括号生成
    Java实现 LeetCode 22 括号生成
    Java实现 LeetCode 21 合并两个有序链表
    Java实现 LeetCode 21 合并两个有序链表
    Java实现 LeetCode 21 合并两个有序链表
  • 原文地址:https://www.cnblogs.com/waw/p/2210671.html
Copyright © 2011-2022 走看看