<%--兼容IE7 必须放在 head 的第一行--%>
独上高楼网站
  • try...catch...finally中的finally一定会执行吗?
  • try...catch...finally中的finally一定会执行吗?

    在windows中,每个线程默认的栈大小是1M,托管线程也一样。在32位windows中,用C#在系统中最多可以创建多少个线程呢?答案稍后说。

    大家都知道try...catch...finally是用来控制异常的流转,一般说来finally是最后一班岗哨,问100个人,99个肯定说一定会执行。是的,一般来讲确实是能执行到的,原因是什么呢?比如在try或catch里return之后为什么还能执行到finally呢?答案是因为return只是把返回值放入相应的地方(一般来讲是寄存器),准备返回;在一个函数返回之前,也就是ret指令调用之前,还有一些代码需要执行,就是清空堆栈(弹出入栈的压入的参数,函数的首地址等),这和函数的调用方式stdcal有关。那么finally有没有机会不执行呢?看代码:

      1     static  void Foo()
     2     {
     3         Foo();
     4     }
     5 
     6     static void Main()
     7     {
     8         int i=0;
     9         try
    10         {
    11             Foo();   
    12         }
    13         catch(Exception ex)
    14         {
    15             ;
    16         }
    17         finally
    18         {
    19             i = 2;
    20         }
    21         
    22     }

    这个递归调用会造成System.StackOverflowException,但是我下面有catch啊?为什么就直接程序直接就结束了呢?

    下面用windbg调试一下:

    CommandLine: "C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\ConsoleApplication2\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe"
    Symbol search path 
    is: c:\symbols;http://msdl.microsoft.com/download/symbols;C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\symbols
    Executable search path is: c:\symbols
    ModLoad: 
    00400000 00408000   ConsoleApplication2.exe
    ModLoad: 7c930000 7ca02000   ntdll.dll
    ModLoad: 
    79000000 79046000   C:\WINDOWS\system32\mscoree.dll
    ModLoad: 7c800000 7c92b000   C:\WINDOWS\system32\KERNEL32.dll
    (bf8.4ec): Break instruction exception 
    - code 80000003 (first chance)
    eax
    =7ca00000 ebx=7ffdf000 ecx=00000001 edx=00000002 esi=7c9b97f4 edi=00151f38
    eip
    =7c94a3e1 esp=0012fb70 ebp=0012fcb4 iopl=0         nv up ei pl nz na po nc
    cs
    =001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
    ntdll
    !DbgBreakPoint:
    7c94a3e1 cc              
    int     3
    0:000> g
    ModLoad: 77f30000 77fdc000   C:\WINDOWS\system32\ADVAPI32.dll
    ModLoad: 77c20000 77cbf000   C:\WINDOWS\system32\RPCRT4.dll
    ModLoad: 76eb0000 76ec3000   C:\WINDOWS\system32\Secur32.dll
    ModLoad: 77eb0000 77f02000   C:\WINDOWS\system32\SHLWAPI.dll
    ModLoad: 77bd0000 77c19000   C:\WINDOWS\system32\GDI32.dll
    ModLoad: 77e10000 77ea0000   C:\WINDOWS\system32\USER32.dll
    ModLoad: 77b70000 77bca000   C:\WINDOWS\system32\msvcrt.dll
    ModLoad: 
    76180000 7619d000   C:\WINDOWS\system32\IMM32.DLL
    ModLoad: 7f000000 7f009000   C:\WINDOWS\system32\LPK.DLL
    ModLoad: 74ae0000 74b45000   C:\WINDOWS\system32\USP10.dll
    ModLoad: 
    48000000 48020000   C:\PROGRA~1\Google\GOOGLE~2\GOEC62~1.DLL
    ModLoad: 71b60000 71b77000   C:\WINDOWS\system32\WS2_32.dll
    ModLoad: 71b50000 71b58000   C:\WINDOWS\system32\WS2HELP.dll
    ModLoad: 79e70000 7a3ff000   C:\WINDOWS\Microsoft.NET\Framework\v2.
    0.50727\mscorwks.dll
    ModLoad: 
    78130000 781cb000   C:\WINDOWS\WinSxS\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.1433_x-ww_5CF844D2\MSVCR80.dll
    ModLoad: 7ca10000 7d1ec000   C:\WINDOWS\system32\shell32.dll
    ModLoad: 77cd0000 77dd3000   C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common
    -Controls_6595b64144ccf1df_6.0.3790.3959_x-ww_D8713E55\comctl32.dll
    ModLoad: 790c0000 79bf6000   C:\WINDOWS\assembly\NativeImages_v2.
    0.50727_32\mscorlib\32e6f703c114f3a971cbe706586e3655\mscorlib.ni.dll
    ModLoad: 774b0000 775e9000   C:\WINDOWS\system32\ole32.dll
    ModLoad: 
    79060000 790b6000   C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorjit.dll
    (bf8.4ec): Stack overflow 
    - code c00000fd (first chance)
    First chance exceptions are reported before any exception handling.
    This exception may be expected and handled.
    eax
    =00a83028 ebx=0012f4ac ecx=00000064 edx=00000000 esi=00000064 edi=00000000
    eip
    =00f90110 esp=00033000 ebp=0012f480 iopl=0         nv up ei pl zr na pe nc
    cs
    =001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
    00f90110 
    56              push    esi

    大家可以看到起始的时候esp是在0012fb70,ebp是在0012fcb4;而异常发生的时候esp在00033000,而ebp在0012f480可以发现两个问题:

    1.0012fb70-00033000=fcb70(十进制是1035120>1MB),所以溢出了

    2.栈是向上增长的,逆着内存地址增长的顺序,所以esp会不停的减小,而ebp在esp下面固定每一次方法调用的基地址。但是这个时候ebp已经是0012f480了,超过了进入Foo之前的esp的地址,溢出了,所以回到了栈底。

    这个时候callstack已经完全回不去了,也就是说方法链不能依着原来的路径返回到调用方法之外,就出现了无法恢复的异常,CLR直接把主线程给kill了,结果进程就退出了。

    换段代码:

     1     static  void Foo()
     2     {
     3         Foo();
     4     }
     5 
     6     static void Main()
     7     {
     8         int i=0;
     9         try
    10         {
    11             Thread t1 = new Thread(new ThreadStart (Foo));
    12             t1.Start();
    13         }
    14         catch(Exception ex)
    15         {
    16             ;
    17         }
    18         finally
    19         {
    20             i = 2;
    21         }
    22         
    23     }

    这个代码里的finally是可以运行的,因为溢出的不是主线程的堆栈,所以kill了也没事。但是同样会抛出System.StackOverflowException异常。 当然,可以通过参数来控制线程最大堆栈大小。可以通过一些tool来观察这些线程的活动,由于时间有限,我就不做了,赶紧洗洗上班去。最后公布答案:用C#创建用户级的线程(如果你非得较真用P/V Invoke来创建内核级线程,我无话可说,也不要用boot.ini /3GB)极限是2048个(往往到不了这个数),因为32系统只有2G内存用于用户态程序,而每个线程默认的栈大小是1M,所以2G/1M=2048个,大家可以运行下面的代码测试一下:

     1 static  void Foo()
     2     {
     3         Thread.Sleep(Int32.MaxValue);
     4     }
     5 
     6     static void Main()
     7     {
     8         
     9         try
    10         {
    11             for (int i = 0; i < Int32.MaxValue; i++)
    12             {
    13                 Thread t1 = new Thread(new ThreadStart(Foo));
    14                 Console.WriteLine(i.ToString ());
    15                 t1.Start();
    16             }
    17         }
    18         catch(Exception ex)
    19         {
    20             Console.WriteLine(ex.ToString ());
    21         }
    22         finally
    23         {
    24            
    25         }
    26         
    27     }

    这段代码来自jeff的一个presentation。在我的机器上运行的是:

     

    不对之处,欢迎大家指正。

  • 与本文主题相关的文章