|
|
Posted on 8/7/2022 9:11:58 PM
|
|
|
|

1: Background
1. Tell stories
A few days ago, when I indexed the article on .NET Advanced Debugging to github, I found an interesting comment, see the article for details, the screenshot is as follows:
It probably means that executing Task.Result under the main thread of Winform will cause a deadlock, I also looked at the reference link in the picture, Stephen is the absolute boss, but this article is mainly about the indoctrination of a large paragraph of text, and it doesn't really let you see what you see, so I will analyze it from the perspective of windbg.
2: Windbg analysis
1. Will it really be deadlocked?
Of course, I haven't played winform for many years, and I can't figure out if it will, at least not in the console.
The code is very simple, run the program, click click, and sure enough, the interface is stuck, which is a bit incredible.
2. Look for the cause of the deadlock
Next, hurry up and attach windbg to the process to find out.
1) Look at the main thread The interface is unresponsive, so naturally the main thread is stuck, so you need to take a look at what the main thread is doing at this time. Use the command ~0s + !clrstack.
0:000> !clrstack OS Thread Id: 0x5a10 (0) Child SP IP Call Site 0000004d10dfde00 00007ffb889a10e4 [GCFrame: 0000004d10dfde00] 0000004d10dfdf28 00007ffb889a10e4 [HelperMethodFrame_1OBJ: 0000004d10dfdf28] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object) 0000004d10dfe040 00007ffb66920d64 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken) 0000004d10dfe0d0 00007ffb6691b4bb System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) 0000004d10dfe140 00007ffb672601d1 System.Threading.Tasks.Task.InternalWait(Int32, System.Threading.CancellationToken) 0000004d10dfe210 00007ffb6725cfa7 System.Threading.Tasks.Task`1[[System.__Canon, mscorlib]]. GetResultCore(Boolean) 0000004d10dfe250 00007ffb18172a1b WindowsFormsApp4.Form1.button1_Click(System.Object, System.EventArgs) [E:\net5\ConsoleApp1\WindowsFormsApp4\Form1.cs @ 26] 0000004d10dfe2b0 00007ffb3a024747 System.Windows.Forms.Control.OnClick(System.EventArgs) 0000004d10dfe2f0 00007ffb3a027b83 System.Windows.Forms.Button.OnClick(System.EventArgs) 0000004d10dfe340 00007ffb3a837231 System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs) 0000004d10dfe400 00007ffb3a7e097d System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32) 0000004d10dfe480 00007ffb3a0311cc System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef) 0000004d10dfe540 00007ffb3a0b0c97 System.Windows.Forms.ButtonBase.WndProc(System.Windows.Forms.Message ByRef) 0000004d10dfe5c0 00007ffb3a0b0be5 System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef) 0000004d10dfe5f0 00007ffb3a030082 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr) 0000004d10dfe690 00007ffb3a765a02 DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int64, Int32, Int64, Int64) 0000004d10dfe9d0 00007ffb776d221e [InlinedCallFrame: 0000004d10dfe9d0] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef) 0000004d10dfe9d0 00007ffb3a0b9489 [InlinedCallFrame: 0000004d10dfe9d0] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef) 0000004d10dfe9a0 00007ffb3a0b9489 DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef) 0000004d10dfea60 00007ffb3a046661 System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop( IntPtr, Int32, Int32) 0000004d10dfeb50 00007ffb3a045fc7 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext) 0000004d10dfebf0 00007ffb3a045dc2 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext) 0000004d10dfec50 00007ffb181708e2 WindowsFormsApp4.Program.Main() [E:\net5\ConsoleApp1\WindowsFormsApp4\Program.cs @ 19] 0000004d10dfee78 00007ffb776d6923 [GCFrame: 0000004d10dfee78] From the stack output, the main thread is finally stuck on Monitor.ObjWait under Task.Result, which means it has not yet fetched the last jsonString, which is very strange, it has been several minutes, is there a problem with the network? My net is 100M full of firepower...
2) Where did jsonString go? If jsonString is found on the managed heap, it means that there is something on the program that makes the result delay, use the command !dumpheap -type String -min 8500 + !do 000001f19002fcf0 to view, as shown in the figure below:
From the figure, it can be clearly seen that html is back, since it's all back, why haven't Task.Result ended yet? The next step is to see who holds this html and use !gcroot.
0:000> !gcroot 000001f19002fcf0 Thread 5a10: 0000004d10dfe250 00007ffb18172a1b WindowsFormsApp4.Form1.button1_Click(System.Object, System.EventArgs) [E:\net5\ConsoleApp1\WindowsFormsApp4\Form1.cs @ 26] rbp+10: 0000004d10dfe2b0 -> 000001f180007f78 WindowsFormsApp4.Form1 -> 000001f180070d68 System.ComponentModel.EventHandlerList -> 000001f180071718 System.ComponentModel.EventHandlerList+ListEntry -> 000001f1800716d8 System.EventHandler -> 000001f1800716b0 System.Windows.Forms.ApplicationContext -> 000001f180071780 System.EventHandler -> 000001f18006ab38 System.Windows.Forms.Application+ThreadContext -> 000001f18006b140 System.Windows.Forms.Application+MarshalingControl -> 000001f18016c9c8 System.Collections.Queue -> 000001f18016ca00 System.Object[] -> 000001f18016c948 System.Windows.Forms.Control+ThreadMethodEntry -> 000001f18016c8b8 System.Object[] -> 000001f1800e6f80 System.Action -> 000001f1800e6f60 System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner -> 000001f1800a77d0 WindowsFormsApp4.Form1+<GetJsonAsync>d__2 -> 000001f1800b4e50 System.Threading.Tasks.Task`1[[System.String, mscorlib]] -> 000001f19002fcf0 System.String
Found 1 unique roots (run '! GCRoot -all' to see all roots). From the output results, this System.String is finally held by WindowsFormsApp4.Form1 of the 5a10 thread, and you can use !t to verify what thread 5a10 is.
0:000> !t Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 5a10 000001f1f1b01200 2026020 Preemptive 000001F1800E70E8:000001F1800E7FD0 000001f1f1ad5b90 0 STA 2 2 712c 000001f1f1b2a270 2b220 Preemptive 0000000000000000:0000000000000000 000001f1f1ad5b90 0 MTA (Finalizer) I go, 5a10 turned out to be the main thread, it was really a bit confusing, the main thread was stuck, and the string was held by the main thread, completely inexplicable.
3) Look for breakout points Still going back and thinking about this reference chain calmly, I found that there is a Queue here: -> 000001f18016c9c8 System.Collections.Queue, I have an idea, I can debug the source code by placing a breakpoint in the Queue, and the tool uses DnSpy, just do it.
As you can see from the figure, when entering Queue, thread 10 is used, which means that the string is not yet held by the main thread at this time.
From the diagram, it can be seen that the continuous task is finally scheduled by WindowsFormsSynchronizationContext.Post into the Queue under Control, and the data in this Queue needs to be executed by the UI thread, so there is the following dialogue:
main thread: task brother, when will you finish executing it? I'm waiting for you to complete the signal?
task:Brother, if you don't execute me, how can I finish it?
Main thread:Oh...
In summary: the continuation task has reached the queue waiting for the main thread to execute, and at this time, the main thread is stunned and has been waiting for the continuation task to complete=true, the problem is here: how can the continuation task not be executed Complete=true? So they are like this.
Three: How to crack it
Knowing the cause and effect, this method of cracking is simple, roughly divided into two types.
1. It is forbidden to drop a continuation task into a queue
To cut off this path, the implication is to let the thread pool end the task by itself, so that the UI thread can sense that the task has been completed, and finally the UI thread can get the final html, which is to add ConfigureAwait(false) after await, as follows:
2. Block the main thread
If the main thread is not blocked, then the main thread can freely obtain the tasks that need to be executed in Control.Queue, and the method is very simple, just add await before GetJsonAsync.
Three: Summary
The conclusion is to do more of your own practical practice, theoretical knowledge is forcibly instilled in you by others, whether it is right or wrong, in fact, you have no bottom in your heart, practical verification is what really belongs to you, and it is hard to forget, after all, you have really experienced, practiced, and verified.
Original:The hyperlink login is visible.
|
Previous:Docker modifies and restricts container CPU, memory, and other resourcesNext:JS arrays are the difference and use of every and some
|