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。在我的机器上运行的是:

 

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

posted @ 2009-07-03 07:30  DiggingDeeply  阅读(7810)  评论(36编辑  收藏  举报