try...catch...finally中的finally一定会执行吗?
在windows中,每个线程默认的栈大小是1M,托管线程也一样。
在32位windows中,用C#在系统中最多可以创建多少个线程呢?答案稍后说。
大家都知道try...catch...finally是用来控制异常的流转,一般说来finally是最后一班岗哨,问100个人,99个肯定说一定会执行。
是的,一般来讲确实是能执行到的,原因是什么呢?比如在try或catch里return之后为什么还能执行到finally呢?答案是因为return只是把返回值放入相应的地方(一般来讲是寄存器),准备返回;在一个函数返回之前,也就是ret指令调用之前,还有一些代码需要执行,就是清空堆栈(弹出入栈的压入的参数,函数的首地址等),这和函数的调用方式stdcal有关。
那么finally有没有机会不执行呢?看代码:
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调试一下:
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了,结果进程就退出了。
换段代码:
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个,大家可以运行下面的代码测试一下:
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。在我的机器上运行的是:
不对之处,欢迎大家指正。