当前位置:网站首页>Don't treat exceptions as business logic, which you can't afford

Don't treat exceptions as business logic, which you can't afford

2020-11-07 20:15:00 One line code agriculture

One : background

1. Tell a story

I've been working on the project for a few years , You should have seen more or less people treat exceptions as business logic (┬_┬), For example, judge whether a number is an integer , Take it for granted try catch wrap up , Proceed again int.Parse, If you throw an exception, it means it's not an integer , Simple and crude , Or there's no need to write regular logic , Another example is the forced conversion of a string to Enum, Direct use Enum.Parse, Maybe it's because you don't know much about the overhead of exceptions , This bad habit may have been discovered by the authorities , The follow-up gave us a lot of Try Prefix method , such as :int.TryParse , Enum.TryParse, dict.TryGetValue , The code is shown as follows :

            // The original way of writing 
            var num = int.Parse("1");

            // Use try The way 
            var result = 0;
            var b = int.TryParse("1", out result);

use Try There is nothing wrong with the series method , But this way is a way to make complaints about people , And define it separately result Variable , Did not withdraw , The authorities still have to rely on us developers to carry forward their development , Finally in the C# 7.0 There's a new out variables Grammatical sugar .


            //try out  Variable mode 
            var c = int.TryParse("1", out int result2);

such out Variable That's the pattern , One method takes two values , There's no risk of exception .

Two : Why use tryxxx Method

With tryxxx After method , You should understand that Microsoft is already reminding us developers not to abuse exceptions , Especially in predictable and predictable situations , After all, they know that the overhead of exceptions is really too much , It's no wonder that I don't know .

1. Low performance visible to the naked eye

To make it visible to the naked eye , We'll use the exception method and tryxxx Method to make a performance comparison , iteration 50w Time , Let's see how they perform ?


            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();

The results are quite frightening , Difference between 480 times , What a familiar number ... 480 temples in the Southern Dynasty , How many buildings in the rain

3、 ... and : Extraordinary high overhead

Why are exceptions so expensive ? Only when you know yourself and your enemy can you have a clear idea , Friends who have seen my multithreaded video should know , The cost of creating and destroying threads is very high , One of them is to switch the code from user mode to kernel mode , After all, threads are operating system level things , And you CLR irrelevant ,CLR It's just a system package , In fact, many people have never thought of , We use try catch finally The bottom layer also encapsulates the operating system level (Windows Structured exception handling ), It's also called SEH, What do you mean ? It's when you throw after , Code needs to switch from user mode to kernel mode , The cost will not be small , Another expense comes from Exception Medium StackTrace, The value in this needs to grab the call stack from the thread stack of the current exception , The deeper the stack , The more it costs .

1. From user mode to kernel mode

I'm sure you'll say , Don't be so mysterious , There's a piece of evidence for everything , Do more,Talk less, I'm going to explain it in two ways .

<1> Yes catch situation

Ready to catch When it's time to block , And grab it dump file .


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

Use !dumpstack Put the present 0 Number thread All managed and unmanaged stacks are typed out of the , 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))

Because it's a stack , So the execution flow should be viewed from the back to the front , You'll find that the process looks like this int.Parse -> CLR -> ntdll -> CLR -> Console.ReadLine , Obviously ntdll.dll Is a core file at the operating system level , This changes from user mode to kernel mode , If not very clear , Let me draw a sketch ...

<2>. nothing catch Handle

You must be curious , If there is no catch What will happen , You can also use windbg Go and dig .


        public static void Main(string[] args)
        {
            var num = int.Parse("xxx");
        }


0:000> !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))

You can see that the exit logic of the process gives the managed program entry mscoreei.dll And never entered again Main Function , So I also make up a picture for you to see

2. Grab thread call stack

When we see the abnormality in a panic , First look at what the abnormal information is ? The second look is to see which line of code the exception is found in , This is the call stack of the thread , This information is very important , It can help us find problems and solve problems quickly , Put it in Exception Of StackTrace in , Let's start with the last piece of code .


    public static void Main(string[] args)
        {
            Run();
            Console.ReadLine();
        }

        public static void Run()
        {
            var ex = new FormatException(" Your format is wrong !!!");
            throw ex;
        }

<1> StackTrace When was it inserted

So far, I haven't seen any book that says StackTrace When was it jammed in ? Due to limited level , I'll also try to probe .

As you can see from the code, it's not in new It's been stuffed in , Where would that be ?

Since it's not in user code , That's it CLR Go to see , stay windbg of use dumpstack To see the unmanaged stack .


0:000> !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())

From the simplified process , Suspicion is caused by clr!HelperMethodFrameRestoreState To deal with the , Why do you say that ? Because we define FormatException ex It will be passed on to CLR Of , If you don't believe it, you can use kb Have a look .


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+0x21


The first line of it is 00 00007fffe07a3181 : 00000000e0434352 0000006d4a7fe938 0000017b30ad2d48 0000017b2f081690 : KERNELBASE!RaiseException+0x68 The address of the third parameter in the 0000017b30ad2d48` It's our exception class , Print it out and have a look .


0:000> !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:     .........../{[email protected]..............................
Fields:
None


here _stackTrace It's worth it , After all Console It's already printed out on .

Finally, I would like to add that you can also use !threads Find the exception thread , As shown in the figure below System.FormatException 0000017b30ad2d48, And then through !printexception To print this address 0000017b30ad2d48 On the exception object .


0:000> !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: 80131537

3、 ... and : summary

Don't treat exceptions as business logic , Maybe you can't afford it , Leave those really unpredictable situations to the exception , Such as : TimeoutException...

If you have more questions to interact with me , Scan below and come in ~

 Image name

版权声明
本文为[One line code agriculture]所创,转载请带上原文链接,感谢