A: background

1. Tell a story

After several years of experience in the project, I should have more or less seen some people treat exceptions as business logic (┬ _ ┬). For example, to judge whether a number is an integer, it is taken for granted to wrap it with try catch and then perform int.parse. Parse (string) {Parse (string) {Parse (string) {Parse (string) {Parse (string) {Parse (string) {Parse (string) {Parse (string);}} Int.TryParse, Enum.TryParse, dict.TryGetValue

Var num = int.Parse("1"); Var result = 0; var b = int.TryParse("1", out result);Copy the code

There is nothing wrong with using the Try method, but the result variable needs to be defined separately. It is still up to us developers to carry it forward 😄😄😄 and finally add an out variables syntax sugar to C# 7.0.

Var c = int.TryParse("1", out int result2);Copy the code

The out variable pattern is 🐮👃, with two values for one method and no risk of throwing exceptions.

Two: why use tryxxx method

With the Tryxxx method, you should know that Microsoft has been warning developers not to abuse exceptions, especially in predictable and predictable scenarios, because they know that exceptions can be expensive.

1. Visible low performance

In order to let you see the naked eye, we use the exception method and tryxxx method to do a performance comparison, iteration 50W times, see how their performance?

for (int i = 0; i < 3; i++) { var watch = Stopwatch.StartNew(); for (int k = 0; k < 50000; k++) { try { var num = int.Parse("xxx"); } catch (Exception ex) { } } watch.Stop(); Console. WriteLine ($I = "{I + 1}, cost: {watch. ElapsedMilliseconds}"); } Console.WriteLine("---------------------------------------------"); for (int i = 0; i < 3; i++) { var watch = Stopwatch.StartNew(); for (int k = 0; k < 50000; k++) { var num = int.TryParse("xxx", out int reuslt); } watch.Stop(); Console. WriteLine ($I = "{I + 1}, cost: {watch. ElapsedMilliseconds}"); } Console.ReadLine();Copy the code

Look at the results are quite scary, 480 times, a familiar number… Southern four hundred and eighty temple, how many building yanyu 😄😄😄

Three: abnormal super overhead

Why are exceptions so expensive? Only the fittest can know fairly well, read my multithreaded video friends should know that the thread creation and destruction of the price is very big, one is the need to code switch from user mode to kernel mode, after all, the thread is the operating system level, the CLR has nothing to do with you, the CLR just do a layer of packaging system, in fact, a lot of people can think We use try Catch Finally which encapsulates the operating system level (Windows structured exception handling), also known as SEH. When you throw, the code needs to switch from user mode to kernel mode, which is not trivial. Another cost comes from StackTrace in Exception, where the value needs to grab the call stack from the thread stack of the current Exception. The deeper the stack, the higher the cost.

1. From user mode to kernel mode

I’m sure you’ll say, don’t be so ridiculous, Do more,Talk less, I’m going to explain it in two ways.

<1> There is a catch condition

Prepare to block on a catch and grab its dump file.

public static void Main(string[] args) { try { var num = int.Parse("xxx"); } catch (Exception ex) { Console.WriteLine(ex.Message); Console.ReadLine(); }}Copy the code

Use! Dumpstack displays all managed and unmanaged stacks for thread 0, simplified as follows:

0:000> ~0s ntdll! NtReadFile+0x14: 00007fff`f805aa64 c3 ret 0:000> ! dumpstack OS Thread Id: 0x2bf0 (0) Current frame: ntdll! NtReadFile+0x14 Caller, Callee (MethodDesc 00007fffde3a40b8 +0x18 System.Console.ReadLine()) (MethodDesc 00007fff810d59f8 +0xa5 ConsoleApp4.Program.Main(System.String[])), calling (MethodDesc 00007fffde3a40b8 +0 System.Console.ReadLine()) 00000044433fc700 00007fffe07a29e0 clr! ExceptionTracker::CallCatchHandler+0x9c, calling clr! ExceptionTracker::CallHandler clr! ClrUnwindEx+0x40, calling ntdll! RtlUnwindEx ntdll! RtlRaiseException+0x4e, calling ntdll! RtlpCaptureContext clr! IL_Throw+0x114, calling clr! RaiseTheExceptionInternalOnly (MethodDesc 00007fffde4f95c0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)), calling mscorlib_ni+0x53976a (MethodDesc 00007fffde3b5330 +0xae System.Number.ParseInt32(System.String, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)), calling (MethodDesc 00007fffde4f95c0 +0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)) (MethodDesc 00007fffde1ebfa8 +0x2eb System.Globalization.NumberFormatInfo.. ctor(System.Globalization.CultureData)), calling (MethodDesc 00007fffde1eba68 +0 System.Globalization.CultureData.GetNFIValues(System.Globalization.NumberFormatInfo)) (MethodDesc 00007fff810d59f8 +0x49  ConsoleApp4.Program.Main(System.String[])), calling (MethodDesc 00007fffde3b1708 +0 System.Int32.Parse(System.String))Copy the code

Parse -> CLR -> NTDLL -> CLR -> console. ReadLine, ntDLL. DLL is a core file at the operating system level. So this is going from user state to kernel state, and if you don’t get it, let me draw a simple picture…

<2>. No catch processing

If there is no catch, you can also use WinDBG to dig.

public static void Main(string[] args) { var num = int.Parse("xxx"); } 0:00 0 >! dumpstack OS Thread Id: 0xd68 (0) Current frame: ntdll! NtTerminateProcess+0x14 Caller, Callee mscoreei! RuntimeDesc::ShutdownAllActiveRuntimes+0x285, calling KERNEL32! ExitProcessImplementation mscoreei! CLRRuntimeHostInternalImpl::ShutdownAllRuntimesThenExit+0x14, calling mscoreei! RuntimeDesc::ShutdownAllActiveRuntimes clr! EEPolicy::ExitProcessViaShim+0x9c clr! SafeExitProcess+0x9d, calling clr! EEPolicy::ExitProcessViaShim ntdll! KiUserExceptionDispatch+0x53, calling ntdll! NtRaiseException clr! RaiseTheExceptionInternalOnly+0x188426, calling clr! EEPolicy::HandleFatalError clr! IL_Throw+0x45, calling clr! LazyMachStateCaptureState (MethodDesc 00007fffde4f95c0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)), calling mscorlib_ni+0x53976a (MethodDesc 00007fffde3b5330 +0xae System.Number.ParseInt32(System.String, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)), calling (MethodDesc 00007fffde4f95c0 +0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)) (MethodDesc 00007fffde1ebfa8 +0x2eb System.Globalization.NumberFormatInfo.. ctor(System.Globalization.CultureData)), calling (MethodDesc 00007fffde1eba68 +0 System.Globalization.CultureData.GetNFIValues(System.Globalization.NumberFormatInfo)) (MethodDesc 00007fff810e59f8 +0x37  ConsoleApp4.Program.Main(System.String[])), calling (MethodDesc 00007fffde3b1708 +0 System.Int32.Parse(System.String))Copy the code

As you can see, the exit logic of the process gives the managed entry to mscoreei.dll and never enters the Main function again

2. Grab the thread call stack

When we see the anomaly in a panic, the first glance will go to see what is abnormal information? This is the call stack of the thread. This information is very important for us to quickly locate the problem and solve it. Put the problem in Exception’s StackTrace and load the code first.

public static void Main(string[] args) { Run(); Console.ReadLine(); } public static void Run() {var ex = new FormatException(); ); throw ex; }Copy the code

<1> when StackTrace was inserted

Haven’t seen any books so far that say when StackTrace was plugged in? Because of the limited level, I also tried to detect a little.

You can see from the code that it wasn’t inserted at new, where would it be?

<2> Look to CLR for answers

Since it’s not in user code, go to the CLR and use the dumpStack in WinDBG to see the unmanaged stack.

0:00 0 >! dumpstack OS Thread Id: 0x4090 (0) Current frame: ntdll! NtTerminateProcess+0x14 Caller, Callee clr! EETypeHashTable::FindItem+0x532, calling clr! NgenHashTable<EEClassHashTable,EEClassHashEntry,4>::PersistedBucketList::GetBucket clr! JIT_StrCns+0xd0, calling clr! HelperMethodFrameRestoreState (MethodDesc 00007fff810f5a08 +0x70 ConsoleApp4.Program.Run()), calling clr! IL_Throw clr! IL_Throw+0x45, calling clr! LazyMachStateCaptureState (MethodDesc 00007fff810f5a08 +0x70 ConsoleApp4.Program.Run()), calling clr! IL_Throw (MethodDesc 00007fff810f59f8 +0x28 ConsoleApp4.Program.Main(System.String[])), calling 00007fff81200488 (stub for ConsoleApp4.Program.Run())Copy the code

From the simplified process, it is suspected that CLR! HelperMethodFrameRestoreState processing, why do you say that? Since the FormatException ex we define will be passed to the CLR, we can use KB to look at it.

0:000> kb # RetAddr : Args to Child : Call Site 00 00007fff`e07a3181 : 00000000`e0434352 0000006d`4a7fe938 0000017b`30ad2d48 0000017b`2f081690 : KERNELBASE! RaiseException+0x68 01 00007fff`e07a45f4 : ffffffff`fffffffe 0000017b`2ef02542 00000000`0000000a 0000017b`2f040910 : clr! RaiseTheExceptionInternalOnly+0x31f 02 00007fff`811d0950 : 00000000`70000001 00007fff`810c4140 0000006d`4a7fedb8 0000006d`4a7fec78 : clr! IL_Throw+0x114 03 00007fff`811d08b8 : 0000017b`30ad2d30 00007fff`810c4140 00000000`00000000 00007fff`00000000 : 0x00007fff`811d0950 04 00007fff`e0736c93 : 0000017b`30ad2d30 00007fff`810c4140 00000000`00000000 00007fff`00000000 : 0x00007fff`811d08b8 05 00007fff`e0736b79 : 00000000`00000000 00007fff`e0737aae 0000006d`4a7fefb8 00000000`00000000 : clr! CallDescrWorkerInternal+0x83 06 00007fff`e0737410 : 0000006d`4a7fefb8 0000006d`4a7ff048 0000006d`4a7feeb8 00000000`00000001 : clr! CallDescrWorkerWithHandler+0x4e 07 00007fff`e08dcaf2 : 0000006d`4a7fee00 00000000`00000001 00000000`00000001 0000017b`2efcecf0 : clr! MethodDescCallSite::CallTargetWorker+0x102 08 00007fff`e08dd4b3 : 00000000`00000001 00000000`00000000 0000017b`30ad2d30 0000017b`30ad2d30 : clr! RunMain+0x25f 09 00007fff`e08dd367 : 0000017b`2f040910 0000006d`4a7ff420 0000017b`2f040910 0000017b`2f082770 : clr! Assembly::ExecuteMainMethod+0xb7 0a 00007fff`e08dccb3 : 00000000`00000000 0000017b`2ef00000 00000000`00000000 00000000`00000000 : clr! SystemDomain::ExecuteMainMethod+0x643 0b 00007fff`e08dcc31 : 0000017b`2ef00000 00007fff`e08de090 00000000`00000000 00000000`00000000 : clr! ExecuteEXE+0x3f 0c 00007fff`e08de0a4 : ffffffff`ffffffff 00007fff`e08de090 00000000`00000000 00000000`00000000 : clr! _CorExeMainInternal+0xb2 0d 00007fff`e1208a61 : 00000000`00000000 00007fff`00000091 00000000`00000000 0000006d`4a7ff9f8 : clr! CorExeMain+0x14 0e 00007fff`e133a4cc : 00000000`00000000 00007fff`e08de090 00000000`00000000 00000000`00000000 : mscoreei! CorExeMain+0x112 0f 00007fff`f5cc4034 : 00007fff`e1200000 00000000`00000000 00000000`00000000 00000000`00000000 : MSCOREE! CorExeMain_Exported+0x6c 10 00007fff`f8033691 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32! BaseThreadInitThunk+0x14 11 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll! RtlUserThreadStart+0x21Copy the code

00 00007FFFE07a3181:00000000e0434352 0000006d4a7FE938 0000017b30AD2D48 0000017b2f081690: KERNELBASE! RaiseException+0x68 the third argument, address 0000017b30AD2d48, is our exception class. Print it out.

0:00 0 >! do 0000017b30ad2d48 Name: System.FormatException MethodTable: 00007fffde285c38 EEClass: 00007fffde3930e0 Size: 160(0xa0) bytes File: C:\WINDOWS\Microsoft.Net \ assembly \ GAC_64 \ mscorlib \ v4.0 _4. 0.0.0 __b77a5c561934e089 \ mscorlib DLL Fields: MT Field Offset Type VT Attr Value Name 00007fffde2059c0 40002a2 8 System.String 0 instance 0000017b30ad4c80 _className 00007fffde282a50 40002a3 10 ... ection.MethodBase 0 instance 0000000000000000 _exceptionMethod 00007fffde2059c0 40002a4 18 System.String 0 instance 0000000000000000 _exceptionMethodString 00007fffde2059c0 40002a5 20 System.String 0 instance 0000017b30ad2de8 _message 00007fffde2883d8 40002a6 28 ... tions.IDictionary 0 instance 0000000000000000 _data 00007fffde205b70 40002a7 30 System.Exception 0 instance 0000000000000000 _innerException 00007fffde2059c0 40002a8 38 System.String 0 instance 0000000000000000 _helpURL 00007fffde205dd8 40002a9 40 System.Object 0 instance 0000017b30ad2e98 _stackTrace 00007fffde205dd8 40002aa 48 System.Object 0 instance 0000017b30ad2f28 _watsonBuckets 00007fffde2059c0 40002ab 50 System.String 0 instance 0000000000000000 _stackTraceString 00007fffde2059c0 40002ac 58 System.String 0 instance 0000000000000000 _remoteStackTraceString 00007fffde2085a0 40002ad 88 System.Int32 1 instance 0 _remoteStackIndex 00007fffde205dd8 40002ae  60 System.Object 0 instance 0000000000000000 _dynamicMethods 00007fffde2085a0 40002af 8c System.Int32 1 instance -2146233033 _HResult 00007fffde2059c0 40002b0 68 System.String 0 instance 0000000000000000 _source 00007fffde2831f8 40002b1 78 System.IntPtr 1 instance 0 _xptrs 00007fffde2085a0 40002b2 90 System.Int32 1 instance -532462766 _xcode 00007fffde21e720 40002b3 80 System.UIntPtr 1 instance 0 _ipForWatsonBuckets 00007fffde1f5080 40002b4 70 ... ializationManager 0 instance 0000017b30ad2e18 _safeSerializationManager 00007fffde205dd8 40002a1 100 System.Object 0 shared static s_EDILock >> Domain:Value 0000017b2efe0af0:NotInit << 0:000> ! do 0000017b30ad2e98 Name: System.SByte[] MethodTable: 00007fffde20dde8 EEClass: 00007fffde390920 Size: 120(0x78) bytes Array: Rank 1, Number of elements 96, Type SByte (Print Array) Content: ........... / {... P....... @.. Jm.... Z......................... Jm.... Y.............................. Fields: NoneCopy the code

At this point, _stackTrace already has a value, since it’s already printed on the Console.

Finally, add that you can also pass! Threads to find the abnormal thread (System.FormatException 0000017B30AD2D48) Printexception to print the exception object at address 0000017b30AD2d48.

0:00 0 >! threads ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 80c 0000016816f508f0 2a020 Preemptive 0000016818CCE3B8:0000016818CCFFD0 0000016816ef0b10 0 MTA System.FormatException 0000017b30ad2d48 6 2 12d8 0000016816f7b0e0 2b220 Preemptive 0000000000000000:0000000000000000 0000016816ef0b10 0 MTA (Finalizer) 0:000> ! printexception 0000017b30ad2d48 Exception object: 0000017b30ad2d48 Exception type: System.FormatException Message: Your format is wrong!! InnerException: <none> StackTrace (generated): SP IP Function 0000001F8F7FEE90 00007FFF811E0951 ConsoleApp4! ConsoleApp4.Program.Run()+0x71 0000001F8F7FEEE0 00007FFF811E08B9 ConsoleApp4! ConsoleApp4.Program.Main(System.String[])+0x29 StackTraceString: <none> HResult: 80131537Copy the code

Three:

Instead of treating exceptions as business logic, which you probably can’t afford, save the truly unpredictable for exceptions, such as timeoutExceptions…