We've seen plenty of questions about when and why to use try
/catch
and try
/catch
/finally
. And I know there's definitely a use case for try
/finally
(especially since it is the way the using
statement is implemented).
We've also seen questions about the overhead of try/catch and exceptions.
The question I linked to, however, doesn't talk about the overhead of having JUST try-finally.
Assuming there are no exceptions from anything that happens in the try
block, what's the overhead of making sure that the finally
statements get executed on leaving the try
block (sometimes by returning from the function)?
Again, I'm asking ONLY about try
/finally
, no catch
, no throwing of exceptions.
Thanks!
EDIT: Okay, I'm going to try to show my use case a little better.
Which should I use, DoWithTryFinally
or DoWithoutTryFinally
?
public bool DoWithTryFinally() { this.IsBusy = true; try { if (DoLongCheckThatWillNotThrowException()) { this.DebugLogSuccess(); return true; } else { this.ErrorLogFailure(); return false; } } finally { this.IsBusy = false; } } public bool DoWithoutTryFinally() { this.IsBusy = true; if (DoLongCheckThatWillNotThrowException()) { this.DebugLogSuccess(); this.IsBusy = false; return true; } else { this.ErrorLogFailure(); this.IsBusy = false; return false; } }
This case is overly simplistic because there are only two return points, but imagine if there were four... or ten... or a hundred.
At some point I would want to use try
/finally
for the following reasons:
this.Working
is set to false
.So hypothetically, given performance concerns, maintainability, and DRY principles, for what number of exit points (especially if I can assume that all inner exceptions are caught) do I want to incur whatever performance penalty is associated with try
/finally
?
EDIT #2: I changed the name of this.Working
to this.IsBusy
. Sorry, forgot to mention this is multithreaded (though only one thread will ever actually call the method); other threads will be polling to see if the object is doing its work. The return value is merely success or failure for if the work went as expected.
There has to be an overhead for try/catch blocks so that the CLR can handle the exceptions. C# runs on the . NET CLR(a virtual machine).
There is no cost to try/catch the only cost is when an exception is thrown, and that is regardless of whatever there is a try/catch around it or not.
Yes, it absolutely will. Assuming your finally block doesn't throw an exception, of course, in which case that will effectively "replace" the one that was originally thrown.
Does try { } catch make program slower? No. There's a performance hit to actually throwing an exception, but if you don't catch it, it'll just propagate up and potentially be unhandled. In the case of exceptions you have no control over such as the standard or third …
Why not look at what you actually get?
Here is a simple chunk of code in C#:
static void Main(string[] args) { int i = 0; try { i = 1; Console.WriteLine(i); return; } finally { Console.WriteLine("finally."); } }
And here is the resulting IL in the debug build:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 1 .locals init ([0] int32 i) L_0000: nop L_0001: ldc.i4.0 L_0002: stloc.0 L_0003: nop L_0004: ldc.i4.1 L_0005: stloc.0 L_0006: ldloc.0 // here's the WriteLine of i L_0007: call void [mscorlib]System.Console::WriteLine(int32) L_000c: nop L_000d: leave.s L_001d // this is the flavor of branch that triggers finally L_000f: nop L_0010: ldstr "finally." L_0015: call void [mscorlib]System.Console::WriteLine(string) L_001a: nop L_001b: nop L_001c: endfinally L_001d: nop L_001e: ret .try L_0003 to L_000f finally handler L_000f to L_001d }
and here's the assembly generated by the JIT when running in debug:
00000000 push ebp 00000001 mov ebp,esp 00000003 push edi 00000004 push esi 00000005 push ebx 00000006 sub esp,34h 00000009 mov esi,ecx 0000000b lea edi,[ebp-38h] 0000000e mov ecx,0Bh 00000013 xor eax,eax 00000015 rep stos dword ptr es:[edi] 00000017 mov ecx,esi 00000019 xor eax,eax 0000001b mov dword ptr [ebp-1Ch],eax 0000001e mov dword ptr [ebp-3Ch],ecx 00000021 cmp dword ptr ds:[00288D34h],0 00000028 je 0000002F 0000002a call 59439E21 0000002f xor edx,edx 00000031 mov dword ptr [ebp-40h],edx 00000034 nop int i = 0; 00000035 xor edx,edx 00000037 mov dword ptr [ebp-40h],edx try { 0000003a nop i = 1; 0000003b mov dword ptr [ebp-40h],1 Console.WriteLine(i); 00000042 mov ecx,dword ptr [ebp-40h] 00000045 call 58DB2EA0 0000004a nop return; 0000004b nop 0000004c mov dword ptr [ebp-20h],0 00000053 mov dword ptr [ebp-1Ch],0FCh 0000005a push 4E1584h 0000005f jmp 00000061 } finally { 00000061 nop Console.WriteLine("finally."); 00000062 mov ecx,dword ptr ds:[036E2088h] 00000068 call 58DB2DB4 0000006d nop } 0000006e nop 0000006f pop eax 00000070 jmp eax 00000072 nop } 00000073 nop 00000074 lea esp,[ebp-0Ch] 00000077 pop ebx 00000078 pop esi 00000079 pop edi 0000007a pop ebp 0000007b ret 0000007c mov dword ptr [ebp-1Ch],0 00000083 jmp 00000072
Now, if I comment out the try and finally and the return, I get nearly identical assembly from the JIT. The differences you'll see are a jump into the finally block and some code to figure out where to go after the finally is executed. So you're talking about TINY differences. In release, the jump into the finally will get optimized out - braces are nop instructions, so this would become a jump to the next instruction, which is also a nop - that's an easy peephole optimization. The pop eax and then jmp eax is similarly cheap.
{ 00000000 push ebp 00000001 mov ebp,esp 00000003 push edi 00000004 push esi 00000005 push ebx 00000006 sub esp,34h 00000009 mov esi,ecx 0000000b lea edi,[ebp-38h] 0000000e mov ecx,0Bh 00000013 xor eax,eax 00000015 rep stos dword ptr es:[edi] 00000017 mov ecx,esi 00000019 xor eax,eax 0000001b mov dword ptr [ebp-1Ch],eax 0000001e mov dword ptr [ebp-3Ch],ecx 00000021 cmp dword ptr ds:[00198D34h],0 00000028 je 0000002F 0000002a call 59549E21 0000002f xor edx,edx 00000031 mov dword ptr [ebp-40h],edx 00000034 nop int i = 0; 00000035 xor edx,edx 00000037 mov dword ptr [ebp-40h],edx //try //{ i = 1; 0000003a mov dword ptr [ebp-40h],1 Console.WriteLine(i); 00000041 mov ecx,dword ptr [ebp-40h] 00000044 call 58EC2EA0 00000049 nop // return; //} //finally //{ Console.WriteLine("finally."); 0000004a mov ecx,dword ptr ds:[034C2088h] 00000050 call 58EC2DB4 00000055 nop //} } 00000056 nop 00000057 lea esp,[ebp-0Ch] 0000005a pop ebx 0000005b pop esi 0000005c pop edi 0000005d pop ebp 0000005e ret
So you're talking very, very tiny costs for try/finally. There are very few problem domains where this matters. If you're doing something like memcpy and put a try/finally around each byte being copied and then proceed to copy hundreds of MB of data, I could see that being an issue, but in most usage? Negligible.
So let's assume there's an overhead. Are you going to stop using finally
then? Hopefully not.
IMO performance metrics are only relevant if you can choose between different options. I cannot see how you can get the semantic of finally
without using finally
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With