Location>code7788 >text

Advanced .Net Debugging 11: The Finale

Popularity:101 ℃/2024-09-20 15:25:01

I. Introduction
This is the eleventh and final post in my Advanced .Net Debugging series. I've written the first eight chapters of the original book, and I was going to continue with chapters 9 and 10, but I gave up on writing chapter by chapter and section by section, and chose the option of filtering the content of both chapters and combining them into a single article, only including the important content. The reason is that I read the last two chapters of the original book, I found that many chapters are a brief introduction to how to use the tool, the explanation is not very detailed. Another more important reason is that [the knowledge involved in advanced debugging] has been included in all the chapters written earlier. If you want to know more detailed content, you can go directly to the original book.
It should also be noted that the original book was written earlier, the tools presented then were older, and there are better, newer tools available to replace them now, which is one reason I chose to combine them into one.
If in the absence of instructions, all the code of the test environment is Net 8.0, if there is a change, I will explain in the project chapter. Okay, without further ado, let's get started with our debugging for the day.

I need to explain the debugging environment in case it's not clear, I've listed the specifics.
Operating system: Windows Professional 10
Debugging tool: Windbg Preview (Debugger Client:1.2306.1401.0, Debugger engine:10.0.25877.1004)
Download address: you can go to Microsoft Store to download
Development Tools: Microsoft Visual Studio Community 2022 (64-bit) - Current Version 17.8.3
Net version: .Net 8.0
CoreCLR source code:Source Code Download
No tests were performed here using either debugger because the content was all too simple to be of much significance.

II. Catalog structure
In order to let you see more clearly, but also for their own easy to find, I made a directory structure, you can intuitively view the layout of the article, the content, you can be targeted to view.

2.1、SOS and Visual Studio 2022 Integration
2.2、NET Framework Source Level Debugging
2.3、CLR Profiler Analyzer
2.3.1、Running the CLR Analyzer
2.3.2、Summary view
2.3.3、Histogram view
2.3.4、Graph view
2.4、WinDbg and CmdTree Commands
A、basics
B、seeing is believing
2.5、Extended diagnostic information
2.5.1、!Verifyobj
2.5.2、!FindRoots -gen | -gen any |
2.5.3、!Heapstat [-inclUnrooted | -iu]
2.5.4、!GCwhere
2.5.5、!ListNearObj
2.5.6、!AnalyzeOOM
2.5.7、!RCWCleanupList
2.6、Backend Garbage Collection
2.7、synchronization
2.7.1、Thread Pools and Tasks
2.7.2、Monitor
2.7.3、Barrier
2.7.4、CountdownEvent
2.7.5、ManualResetEventSlim
2.7.6、SemaphoreSlim
2.7.7、SpinWait and SpinLock

III. Debugging the source code
Without further ado, this section is the source code part of the debugging, there is no code, of course, can not talk about testing, debugging must have a carrier.

3.1、ExampleCore_9_01

 1 namespace ExampleCore_9_01
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Thread thread = new Thread(delegate ()
 8             {
 9                 ("Hello, World:");
10             });
11             ();
12 
13             ();
14         }
15     }
16 }
View Code


IV. Fundamentals
In this paragraph, some subsections may contain two parts, A and B, or only A. If only part A is included, the letter A will be omitted. A is [Basics], explaining the necessary knowledge points, and B is [Seeing is Believing], proving the knowledge points explained through debugging.

4.1, SOS and Visual Studio 2022 Integration
Net Framework version
The SOS Debugging Extension () helps you debug managed programs in Visual Studio and the Windows Debugger () by providing information about the internal Common Language Runtime (CLR) environment. This tool requires you to enable unmanaged debugging for your project. Automatically installed with the . To use it in Visual Studio, install the Windows Driver Kit (WDK).
To use the SOS debugging extension in Visual Studio, install theWindows Driver Kit (WDK)
If you want to see it in more detail, you can check it out by clicking on the link:(SOS debugging extension)

Net version
To learn more about debugging the cross-platform version, you can click here for more details:SOS Debugging Extension

Combining the power and ease of use of Visual Studio with SOS's parsing of the CLR makes it possible to deal with a variety of complex debugging situations.


4.2 NET Framework Source Level Debugging
Typically, any .NET program makes use of a set of different types defined in the .NET Framework. These types include both simple data types and complex Web service binding types that effectively shield the underlying complexity of these technologies. As with any abstraction, the debugging process is complicated by the fact that if you are debugging a problematic application, the debugging process will be complicated as a result. Recognizing the need to analyze why a program is failing, the debugging process is easier if you can attempt to analyze the source code directly, rather than using reverse engineering (i.e., disassembly.) Microsoft has made portions of the .NET Framework's source code publicly available. This publicly available source code can be integrated into Visual Studio, allowing developers to perform source-level debugging of released .NET Framework source code. Next, we will see how to configure Visual Studio for seamless .NET Framework source-level debugging.
Net Framework and cross-platform Net Framework source code debugging or some differences, I gave Microsoft specific learning page, you study it.
Net Framework platform, we can click this link to configure for framework source code debugging:Debugging .NET Framework Source Code
If it is under the cross-platform version, we click on the following link to frame the learning of source debugging on our own:Debugging .NET and Core Source Code with Visual Studio
The tutorial is very detailed and the method is very simple, so I won't go into it.

4.3. CLR Profiler Analyzer
The CLR Analyzer is a very powerful tool that analyzes the behavior of the managed heap and displays the results in a variety of different formats.
Official website download address:CLRProfiler
If you want to know more and more details, you can click on the connection:CLR probes and Windows Store apps

SOS and CLR Analyzer
To generate files that the CLR parser understands, you can use the SOSTraverseHeap command. The file can then be loaded into the CLR parser to utilize the power of the CLR parser.

Although the CLR Analyzer is a very mature tool that can provide a great deal of information about the managed heap, there are some limitations:
I. Reduce the execution speed of the program. When running a program under the CLR analyzer, the execution speed is reduced by a factor of 10 to 100.
II. the size of the log file. the CLR analyzer collects a large amount of data that is saved on the local drive.
III. The program must be started by the CLR analyzer, not by attaching the CLR analyzer to an already running program.

4.3.1 Running the CLR Analyzer

We can start the CLR Analyzer from the command line: in the execution installation folder (in my case E:\Software\DebugTools\CLRProfiler45Binaries\). Note that the CLR Analyzer supports both x86 and x64 modes, as shown here:



It is therefore important to ensure that the correct mode is activated:

1 E:\Software\DebugTools\CLRProfiler45Binaries\64>

or

1 E:\Software\DebugTools\CLRProfiler45Binaries\32>

If the wrong architecture mode is selected it will prompt:

After the CLR Analyzer has been started, the main window is displayed:

Now, click [Start Desktop App...]. or the [Start Windows Store App...] button, a dialog box will pop up to select the program to be analyzed. Of course, we can also attach a running process to be analyzed by using the [Attach Process] button. If we add a valid application, the program will start running and two other buttons are enabled ([Kil Application] and [Show Heap now]). At this point, you can either wait for the program to finish running (in which case the Summary view will be displayed automatically), or the program will display a user prompt and wait until the user presses any key.

It should be noted that since my test environment is .Net 8.0, when we select the program to be analyzed through the [Start Desktop App...] button, we must set the [Target CLR] to [V4 Core CLR]. When we select the program to be analyzed through the [Start Desktop App...] button, we must set the [Target CLR] to [V4 Core CLR], as shown in the figure:

If you select another option, an error will be prompted:

If our program needs to accept command line parameters, you can click on the [File] menu and select [Set Parameter] to bring up a new window:

Click the [OK] button to return to the main window.

You can now use the [View] menu to select any of the views in order to observe detailed information about the program's operation. Note that some views may not be displayed until the program has finished executing. At this point, go to the command line where the CLR analyzer was started and press any key until the program execution is complete.
The CLR Analyzer saves all the analyzed data in a log file located under the folder C:\Windows\Temp. Of course this path can be modified, as shown:


4.3.2 Summary view
We run CLRProfiler through the command line tool, first enter the directory where CLRProfiler is located, in the address bar directly enter the [cmd] command, open the [cmd] command line tool, as shown in Figure:

The command line tool is shown:

Directly enter, open the [CLR Profiler] tool, the effect is as shown:

Since my program needs parameters, click [File] ---- [Set Parameter] to open the parameter setting window and set the parameters for the command line.

Click [OK] to return to the main interface. Now set [Target CLR] to [V4 Core CLR] and click [Start Destop App...]. to select our application. The CLRProfiler has finished running and the console output is shown in the figure:

CLR Profiler After program execution is complete, the CLR Profiler automatically displays the Summary view of the analysis session, as shown here.

The Summary view contains five main groups of information.
Group I [Heap Statistics]gives general information about the heap during the analysis session.
Allocated bytes gives the total size of all objects allocated on the managed heap.
Relocated bytes gives the total size of the objects that were moved during garbage collection.
Final heapbytes gives the size of all objects on the managed heap when the program exits, which may contain objects with terminators that have not yet been collected, even though they do not have root object references.
Objects finalized gives an indication of how many objects have successfully executed the Finalize method.
Critical objects finalized gives an indication of how many objects have successfully executed the Critical Finalize method. Each of these objects contains a Critical Finalize method, which gives these objects stronger guarantees than other normal objects.
The second group is Garbage Collection Statistics.This part of the information is very simple. This part of the information is very simple, showing the number of garbage collections that occurred in each generation (0-2), and Induced collection indicates the number of garbage collection operations explicitly performed (e.g., via the GC. Collect method).
The third group is [GC Handle Statistics].. It is very useful in tracking the use of handles in a program. It gives the number of handles created during the life of the program, the number of handles destroyed, and how many handles still existed at the end of the program run.
The fourth group is Garbage Collector Generation Sizes.This section gives the average size of each generation (including the large object heap) over the course of the program.
The fifth group is "Profiling Statistics".. This section also gives detailed information about the analysis process itself, such as how many heap dump files were generated during the analysis and the number of comments that were added to the log file.
Some of the grouping boxes also contain associated buttons. For example, the HeapStatistics group has a Histogram button next to each city, and the Garbage Collection Statistics group has a Time Line button. Each of these buttons gives some other view of the collected data.

4.3.3 Histogram view
This section will have to use the images from the original article.
We begin with the [Histogram Alocated Types] view (click on the menu items View, Histogram AllocatedTypes, etc., in turn). The view obtained during the analysis is given in the following figure.

[Histogram Alocated Types] is divided into four main sections. The histogram gives the categories of memory allocations performed in the program according to size. For example, there are 39KB objects that are larger than 16KB but smaller than 32KB. During the analysis, the largest bar graph represents objects that are smaller than 64KB but larger than 32KB. The total size of all objects in this category is 477MB, which occupies 99.98% of the usage in the managed heap. In the right panel, objects can be associated with the color of the columns. Here the column is red, indicating a System,Byte[] type. The top two panels allow to control the maximum value of the panel's vertical axis and the maximum value of the panel's horizontal axis.
By analyzing the [Histogram Allocated Types] view, we can conclude that the managed heap contains a large number of []. Knowing which types take up the most space in the managed heap is a very useful piece of information, and it is also useful to find out where in the source code these allocations are performed. This can be easily done by right clicking on the column to be analyzed and selecting the "Show Who Allocated" menu item. An Allocation Graph view appears, as shown below.

As you can see from the above figure, the allocation diagram indicates that the root stack frame (the main entry point) calls the Main method of the Fragmen class, which in turn calls the Run method, which will ultimately allocate a [] instance.
Another useful piece of information is the Histogramby Age view, which will give you an idea of how long the object has actually been alive. An example of the Histogram by Age view is shown below. Here we can see that the 239MB [] has been alive for 100 to 150 seconds. As with the other histogram views, the Allocation Graph can be obtained by right clicking on the columns and selecting the "Show Who Allocated" menu item.


4.3.4 Graph view
In addition to the Histogram view, there are other views that are also very useful. For example, the Heap Graph view gives you all the objects on the managed heap and their corresponding connections. An example of a Heap Graph view is shown in Figure 9-17.

As you can see from the above figure, [] occupies 239MB of space, while the handle (of type Pinned) occupies 238MB of space.

4.4 WinDbg and CmdTree Commands
A. Basics
We use unmanaged debuggers based on the console versions, ntsd and cdb, respectively. there is also another unmanaged debugger, WinDbg, which is a GUI counterpart to the console debugger. The commands we have discussed so far are available in all three debuggers, but there are some commands that can only be used in WinDbg, such as the undisclosed cmdtree command. This command automates a great deal of typing that would otherwise be required when using the various debugger commands. cmdtree displays a window containing a hierarchical view of commands that the user can double-click on and execute. Typically, commands are grouped by area of functionality such as threads, processes, modules, and so on. The commands displayed in this window can be customized by writing a simple text file that conforms to the syntax layout of the cmdtree command. After writing this text file, you can have it loaded with the cmdtree<filename> command.
As you can see, this cmdtree file can easily be expanded to include other very useful commands, which can save a lot of time when debugging. So instead of typing commands in the command window, we can just double-click to get fast and efficient results.

B. Seeing is believing
Debugging source code: ExampleCore_9_01
Debugging task: Familiarize yourself with the use of the [.cmdtree] command.

1)、Windbg Preview Debugging
Compile the project, this project doesn't need real code, it's just a vehicle. Click [File] ---- [Launch executable] in order to load our ExampleCore_9_01.exe executable and enter the debugger.
Next, we're going to write a command file with the extension [.wl], and my filename is , which reads as follows:

 1 windbg ANSI Command Tree 1.0
 2 
 3 title {"Common Commands"}
 4 
 5 body
 6 
 7 {"Common Commands"}
 8 
 9  {"Information"}
10 
11   {"Time of dump"} {".time"}
12 
13   {"Process being debugged"} {"|"}
14 
15   {"Dump Location"} {"||"}
16 
17   {"Create server on port 9999"} {".server tcp:port=9999"}
18 
19   {"Show remote connections"} {".clients"}
20 
21   {"Process Environment Block"} {"!peb"}
22 
23  {"Logging"}
24 
25   {"Open Log"} {".logopen /t /u /d"}
26 
27   {"Close Log"} {".logclose"}
28 
29  {"Modules"}
30 
31   {"All Modules"} {"lm D sm"}
32 
33   {"Loaded Modules"} {"lmo D sm"}
34 
35   {"Loaded Modules (verbose)"} {"lmvo D sm"}
36 
37   {"Modules w/o symbols"} {"lme D sm"}
38 
39  {"Stacks"}
40 
41   {"Set frame length to 2000"} {".kframes 2000"}
42 
43   {"Dump current stack w/ DML"} {"kpM 1000"}
44 
45   {"Dump stacks without private info"} {"knL 1000"}
46 
47   {"Dump stacks with all parameters"} {"kPn 1000"}
48 
49   {"Dump stacks (distance from last frame)"} {"kf 1000"}
50 
51   {"Dump stacks with Frame Pointer Omission"} {"kvn 1000"}
52 
53   {"Dump all stack"} {"~*kbn 1000"}
54 
55   {"Dump unique stacks"} {"!uniqstack -pn"}
56 
57   {"Thread environment block"} {"!teb"}
58 
59   {"Move to next frame"} {".f+"}
60 
61   {"Move to previous frame"} {".f-"}
62 
63  {"Memory"}
64 
65   {"Dump heaps"} {"!heap -a"}
66 
67  {"Automated Task"}
68 
69   {"!analyze"} {"!analyze -v"}
70 
71   {"Locks"} {"!"}
72 
73   {"CPU time for User and Kernel Mode"} {"!runaway 7"}
74 
75  {"Managed"}
76 
77   {"Load sos"} {".loadby sos mscorwks"}
78 
79   {"clrstack"} {"!clrstack"}
80 
81   {"Threads"} {"!threads"}
82 
83   {"Stack Objects"} {"!dso"}
84 
85   {"Exceptions"} {"!dae"}

The first line indicates the header of the file, pointing out that this file conforms to the format of the Command Tree version 1.0 commands. the title command indicates the title of the command window, and the body command indicates what is displayed in the actual window. Each line in the body section either corresponds to a tree node (in which case no corresponding command is specified) or a leaf node (in which case the name of the command is immediately followed by the actual command, which is executed by the debugger after the node is double-clicked).
Just use the normal txt format for this file, and finally modify the file extension [.wl].
The directory of this file is F:\Test\CmdTreeSample, and the full address is: F:\Test\CmdTreeSample\, go back to the debugger and execute [.cmdtree F:\Test\CmdTreeSample\] directly.

1 windbg> .cmdtree F:\Test\CmdTreeSample\

The effect is shown in the picture:

The effect is so obvious that you can just double-click on the commands inside to execute them directly.


4.5 Extended diagnostic information
In CLR 4.0, the SOS debugger extension includes a new set of commands that can further help us identify GC-related problems in our programs. We will discuss some of the new commands and how they are used.
All the test source code used here is: ExampleCore_9_01, there is no specific test process here, the results are given directly. No [NTSD] debugging test, the effect is the same.

4.5.1、!Verifyobj <object address>
The argument to the command is an object address that determines whether the object is corrupted. The algorithm for detecting whether an object is corrupted is to determine whether the table of methods in the object and its containing objects is complete. If a heap corruption is suspected, it can be quickly determined from the output of this command.

1 0:000> !verifyobj 0x00000239ab009688
2 object 0x239ab009688 is a valid object


4.5.2、!FindRoots -gen <N> | -gen any | <object address>
It is a complex process to find out why a particular object has not been collected yet. If the root object is simple, then it is not difficult to find out what it references, but there are times when it is not easy to find out the root object reference of an object. For example, if a Cross-Generational reference is included in an object and the referenced generation has not yet been collected, then the object still appears to be alive. To make the job easier when these cross-generational references are detected, use the FindRoots command.
The FindRoots command instructs the runtime to set a breakpoint that can be set to be triggered the next time garbage collection occurs in the specified generation (using the gen <N> switch), or whenever a garbage collection operation occurs (using the gen any switch). After a breakpoint has been triggered, the FindRoots command will get the address of an object, and the result of the command's execution will be a root object that displays this object.

4.5.3、!Heapstat [-inclUnrooted | -iu]
The !HeapStat command will give detailed information about the number of bytes used and the number of free bytes in each generation on each managed heap. In addition, it will also give the ratio percentage of bytes used to bytes free on the Small Object Heap (SOH) and Large Object Heap (LOH)).
The default output of the command contains all objects for which a root object reference exists. the incIUnrooted (or abbreviated iu) switch indicates that it contains all objects for which a root object reference exists as well as those for which a root object reference does not exist.

 3 0:000> !heapstat
 4 Heap     Gen0       Gen1       Gen2       LOH        POH        FRZ       
 5 Heap0    83544      0          0          0          16368     
 6 Total    83544      0          0          0          16368     
 7 
 8 Free space:
 9 Heap     Gen0       Gen1       Gen2       LOH        POH        FRZ       
10 Heap0    96         0          0          0          0          SOH:0%              POH:0%  
11 Total    96         0          0          0          0         
12 
13 Committed space:
14 Heap     Gen0       Gen1       Gen2       LOH        POH        FRZ       
15 Heap0    135168     4096       4096       4096       69632     
16 Total    135168     4096       4096       4096       69632     


4.5.4、!GCwhere <object address>
In previous articles we described the process of finding out how to find out what generation an object belongs to. This process consists of dumping the managed heap memory segment (via the eeheap command) and then comparing the address of the object with the heap memory segment given in the output (where the generation corresponding to each heap memory segment is given). If one only wants to find the generation of one or two objects, then this process might work, but if one wants to find the generation of multiple objects, then this process will become very tedious. A command has been introduced in SOS 4.0 which will show some information about the object.

1 0:000> !gcwhere 0x00000239ab009688
2 Address          Heap   Segment          Generation Allocated               Committed               Reserved               
3 0239ab009688     0      0279bc6cf320     0          239ab000028-239ab014680 239ab000000-239ab021000 239ab021000-239ab400000

The address of the object (0x0239ab009688) is given in the output and this object belongs to the generation (Generation 0), Managed Heap (0), Memory Segment Pointer (0x0279bc6cf320), Start Address of Memory Segment (0x239ab000028), Number of Bytes Allocated on Memory Segment (239ab000028-239ab014680).


4.5.5、!ListNearObj<object address>
We can use the [!ListNearObj] command to verify heap consistency. The argument to this command is an object address that will attempt to verify the objects before and after the specified object.

1 0:000> !ListNearObj 0x00000239ab009688
2 Before:              0239ab009648 64 (0x40)                        
3 Current:             0239ab009688 72 (0x48)                        
4 Next:                0239ab0096d0 64 (0x40)                        +StartHelper
5 Heap local consistency confirmed.

The output is divided into before, current and after, followed by the result of the validation operation. The address, size, and type of the object are specified in the before, current, and after sections. In the previous example, all three objects are considered valid, so this command considers the consistency of the heap to be complete.


4.5.6、!AnalyzeOOM
Previous articles described how to analyze the managed heap to get more information about memory exhaustion exceptions. A new command, AnalyzeOOM, has been introduced in SOS 4.0 that can help in the process of diagnosing memory exhaustion problems.

1 0:000> !AnalyzeOOM
2 There was no managed OOM due to allocations on the GC heap

I'm just a simple program here, no how to valid code, so prompt this.

4.5.7、!RCWCleanupList
The RCWCleanupList, which was available in SOS 2.0 but not publicized, has been fully publicized in SOS 4.0. This command displays all runtime callable wrappers that are no longer in use and will be cleared.

1 0:007> !RCWCleanupList
2 Free-Threaded Interfaces to be released: 0
3 MTA Interfaces to be released: 0
4 STA Interfaces to be released: 0


4.6. Background garbage collection
Prior to CLR 4.0, the garbage collector worked in two different modes. The first mode was workstation mode (also called concurrent GC), targeting programs that ran on workstations, e.g., graphical interface programs, Winform, WPF, MVC, etc. The second mode is server mode (also called blocking GC), where the target is usually a server-side program that does not require any graphical interface. The difference between these two modes is mainly in the response time when performing garbage collection. As discussed in previous articles, during the execution of garbage collection, the execution engine and the associated managed threads must be hung periodically to avoid triggering another garbage collection operation. Obviously, this hanging of the managed threads brings about a short stop, which has some impact on the user using the program. In applications with graphical interfaces, this can lead to flickering of the interface, or other subtle phenomena such as a delay between the user clicking a button and getting a response. In these cases, it is important to ensure that the thread is in a hung state for as little time as possible.
Workstation GC (Concurrent GC) uses an approach that hangs all managed threads only twice in GC instead of for their entire lifecycle. During the time that managed threads are not hung, they can continue to allocate memory until they reach the end of the temporary memory segment. If the temporary memory segment runs out of space and a workstation GC is being executed, the managed thread will hang until the workstation GC completes (converting the workstation GC to a server GC). This means that as long as the temporary memory segment is not exhausted, latency can be avoided.
On the other hand, server GCs do not have to be as concerned about real-time response times as workstation GCs, since most server programs do not require real-time response times. Instead of allowing memory to be allocated during the execution of the GC, server-mode GC hangs all threads during the entire execution of the GC. While this may result in some sort of unseen latency time during the execution of the GC, the benefit is that higher throughput is gained, as there is no need to worry about other managed threads that may be executing at the same time. Additionally, in server GC, each processor has a dedicated GC thread, and thus X GCs can be run at the same time (where X is equal to the number of processors multiplied by the number of cores in each processor).
One of the main drawbacks of workstation GC is that it usually only works better on programs with small managed heaps (remember that managed threads will continue to allocate memory as long as temporary memory segments are not exhausted). In practice, it is not uncommon to have several GB bytes of managed heap in a program. In these cases there is still latency because the workstation GC (concurrent mode) changes to a server GC (blocking mode) when the limit of the temporary memory segment is reached. To address this inefficiency, CLR 4.0 replaces concurrent GC with background GC.
To address this inefficiency, CLR 4.0 replaces workstation GC (concurrent mode) with background GC.The main difference between workstation GC and background GC is that background GC allows GC and memory allocation operations to be executed simultaneously, and allows collection of both generation 0 and generation 1 objects. The background GC will periodically check to see if some concurrent memory allocation has resulted in the execution of a GC operation in a temporary memory segment, and if so, will hang itself and allow GC execution in the temporary memory segment (foreground GC). Thus, although a full GC was executed, we were still able to delete objects that survived briefly. Since background GCs are allowed to perform collection operations in generations 0 and 1, what happens when the threshold of the temporary memory segment is reached? In this case, the foreground GC will add temporary memory segments as needed.
In general, server GCs usually block during GC execution. To avoid causing latency time in such hangs, workstation GC was introduced, which minimizes the amount of time a thread is in a hang state during GC. While this approach can work for programs with relatively small managed heaps, it can still cause latency time if temporary memory segments are exhausted. This drawback has led to the evolution of workstation GC to background GC, which allows memory allocation operations and the collection of temporary memory segments to be performed truly concurrently (and the segments to be extended if needed).

Note that in CLR 4.0, background GC is only valid in workstation mode.

4.7、synchronization
4.7.1. Thread Pools and Tasks
The content here is too old to be overly introductory. This section covers thread pool improvements and TPL (parallel programming), so I'll just present the official Microsoft link and let you learn on your own.
Net Advanced Programming if people want to learn :Advanced .NET Programming Documentation
There is a lot of content here, both on APM asynchronous programming and TPL parallel programming.

4.7.2、Monitor
NET4.0 introduced an overloaded version of the method: public static void Enter(object obj, ref bool lockTaken), where the lockTaken parameter is TRUE if a lock is acquired and FALSE otherwise, classically using the following code:

 1 internal class Program
 2 {
 3     private static object LockObject = new object();
 4     static void Main(string[] args)
 5     {
 6         bool acquired = false;
 7         try
 8         {
 9             (LockObject, ref acquired);
10         }
11         finally
12         {
13             if (acquired)
14             {
15                 (LockObject);
16             }
17         }
18         ("Hello, World!");
19     }
20 }

Of course, in .Net 8.0, many other methods have been added. As shown in the figure:

 
 
4.7.3、
A fence object() is best thought of as a way to serialize a series of operations by including one or more checkpoints in the fence object that must first be reached before this operation can be completed. For example, suppose a task requires filling a set of buffers and then using the data in the buffers to perform various computations. Before the second phase (computation node) can start, all the buffers need to be filled first. We can create a fence object containing X participants, each of which must wait at the fence after executing each stage (write, read) for the other threads to finish executing before continuing together.
If you want to know more, you can click on the link:Barier

4.7.4、CountdownEvent
The CountdownEvent class () is a counting event that fires only when its count value is zero.
If you want to know more, you can click on the link:CountdownEvent

4.7.5、ManualResetEventSlim
The ManualResetEventSlim class() is somewhat similar to the existing ManualResetEvent. the key difference between the two is that while waiting to acquire a lock that has already been acquired, the newly-introduced ManualResetEventSlim principle will first spin up. If the lock cannot be acquired within the specified spin time then it will go into a waiting state (as in the case of ManualResetEvent). Since by default it does not go directly to the wait state, there is no need to allocate a data structure containing the wait state, which is why Slim (meaning lightweight) is included in the class name. Note that unlike the ManualResetEvent class, the Slim version can only be used in a cross-process context.
If you want to know more, you can click on the link:ManualResetEventSlim

4.7.6、SemaphoreSlim
Much like ManualResetEventSlim discussed earlier, the Semaphore class also contains an efficient (spin) version called SemaphoreSlim(). Note that unlike the Semaphore class, the Slim version can only be used in a cross-process context.
If you want to know more, you can click on the link:SemaphoreSlim

4.7.7, SpinWait and SpinLock
Spin is a more efficient way to wait for a lock to be released when the average holding time of a lock is very short. The main reason for this is that there is no need to allocate a resource (e.g., an event) to enter the wait state in spin, and the thread will check to see if the lock is released during the spin. The overhead of spin is much less than the overhead required when implementing efficient waits. You can use the SpinLock class() if you only want to use the spin feature of the lock, or the SpinWait class() if you want to spin for a period of time before entering the wait state.
The SpinLock structure is a low-level mutually exclusive synchronization primitive that spins while waiting to acquire a lock. On multicore computers, SpinLock outperforms other types of locks if wait times should be shortened and contention minimized. However, it is recommended that SpinLock be used only if the Determine or Interlocked methods are analyzed to significantly degrade program performance.
is a lightweight synchronization type that can be used in low-level scenarios to avoid the high-cost context switches and kernel transitions required to perform kernel events.
If you want to know more, you can click on the link:SpinWait respond in singingSpinLock


V. Summary
This series has finally been written, and the time span is not short, in fact, it is already the second time to read, understand and debug the process, I found that each time there are new discoveries, and the understanding of the content is more profound. In the first time, each knowledge point was sporadic and incoherent, and there was no way to make it live. The second time, a lot of knowledge points can be connected, each step of the operation also know why, that is, as the saying goes, to know the reason why. Or as the saying goes, read a thousand times the meaning of the self, I now have a deeper understanding of the connotation of this sentence. Net advanced debugging this road, but also just started, there are still a lot of places to learn. The sky is not responsible for those who have the heart, do not forget the original intention, continue to work hard, do what you like to do, happy.