A: background
1. Tell a story
A few days ago, I found an interesting comment while indexing some of the.NET Advanced Debugging articles to GitHub, as shown in the screenshot below:
I also read the reference link in the picture, Stephen is absolutely the big one, but this article is mainly about the cause of the deadlock in a long paragraph of words, it does not really let you see is believe. In this article, I will analyze it from the perspective of Windbg.
2. WINDBG analysis
1. Is it really deadlock?
I haven’t played Winform for years, and I’m not sure if it does, at least not in the Console. Let’s start with a test code.
public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { var jsonTask = GetJsonAsync("http://cnblogs.com").Result; textBox1.Text = jsonTask; } public async static Task<string> GetJsonAsync(string uri) { using (var client = new HttpClient()) { var jsonString = await client.GetStringAsync(uri); return jsonString; }}}
The code is very simple, run the program, click click, sure enough, the interface is stuck, a bit incredible.
2. Find the cause of the deadlock
Now get out the windbg and attach it to the process to find out.
1) View the main thread
The interface is not responding. Naturally, the main thread is stuck, so it is urgent to check what the main thread is doing at this time. Use the command ~ 0S +! Clrstack can.
0:00 0 >! 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.FPushMes sageLoop(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]
According to the stack output, the main thread is stuck in Monitor. Objwait under task.result. This means that it has not received its final jsonString. My net is 100 meters full blast… 🤔 🤔 🤔
2) Where is Jsonstring?
A good way to determine if there is a problem with the network is to search the managed heap by force. If jsonString is found on the managed heap, then something in the program is preventing the Result from ending. dumpheap -type String -min 8500 + ! Do 000001F19002FCF0 can be viewed, as shown below:
It is clear from the figure that HTML is back. If it is back, why not let Task.result end? The next step is to see who owns and uses this HTML! Gcroot can.
0:00 0 >! 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).
This String is held by WindowsFormsApp4.Form1 on the 5a10 thread. T Verify what thread 5a10 is.
0:00 0 >! 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)
5a10 is actually the main thread. It’s really a bit confusing. The main thread is stuck and the string is held by the main thread.
3) Look for breakthroughs
So let’s go back and think about the chain of references, and I see that there is a Queue: -> 000001f18016c9c8 System.Collections.Queue -> 000001f18016c9c8 System.Collections.Queue -> 000001f18016c9c8 System.
As you can see, when we enter our Queue, we are using thread 10, which means that the string is not held by the main thread at this point. If we look at the call stack more closely, I think you should make it clear. Anyway, I have this picture in my head after I read it.
Can be found from the figure, the continuation of the Task was finally WindowsFormsSynchronizationContext. Post scheduling under the Control of the Queue, and the data needs to be in the Queue UI thread to perform, Hence the following dialogue:
Task, when will you finish executing? I am waiting for your signal.
Task: Elder brother, I am already at your home, when will you come to pick me up?
The task needs the main thread to execute it, but the main thread is waiting for the task to complete, so the continued task is never executed. This creates an awkward situation. 🤔 🤔 🤔
Three: the method of cracking
Knowing the cause and effect, the method of solving the problem is simple, and there are generally two kinds.
1. Disallow throwing continued tasks into Queue
To disconnect this path, the thread pool should terminate the task itself so that the UI thread can know that the task has completed and eventually the UI thread can get the final HTML by adding ConfigureAwait(false) after “await” as follows:
2. Do not block the main thread
If you do not block the main thread, then the main thread is free to fetch tasks from control.queue. The change is as simple as adding “await” to getJsonAsync.
Three:
Theoretical knowledge is forced to inculcate to you by others. In fact, you don’t know whether it is right or not. Practical verification is what really belongs to you, and it is hard to forget, after all, you have really experienced, practiced and verified.
For more high-quality dry goods: see my GitHub:dotnetfly