<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一句 Task.Result 就死鎖, 這代碼還怎么寫?

          共 12227字,需瀏覽 25分鐘

           ·

          2021-05-11 22:24


          一:背景

          1. 講故事

          前些天把 .NET 高級(jí)調(diào)試 方面的文章索引到 https://github.com/ctripxchuang/dotnetfly 的過程中,發(fā)現(xiàn)了一個(gè)有意思的評(píng)論,截圖如下:

          大概就是說在 Winform 的主線程下執(zhí)行 Task.Result 會(huì)造成死鎖,我也看了圖中的參考鏈接, Stephen 是絕對(duì)的大佬,不過這篇文章對(duì)死鎖的成因主要還是大段的文字灌輸,沒有真的讓你眼見為實(shí),那這篇我就從 windbg 的角度來給它剖析下。

          二:windbg 分析

          1. 真的會(huì)死鎖嗎?

          看文章看截圖貌似真的會(huì)死鎖,當(dāng)然我多年不玩 winform 了,也搞不清楚到底會(huì)不會(huì),至少在 Console 中是不會(huì)的,得,先上一段測(cè)試代碼。


              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<stringGetJsonAsync(string uri)
                  {
                      using (var client = new HttpClient())
                      {
                          var jsonString = await client.GetStringAsync(uri);

                          return jsonString;
                      }
                  }
              }

          代碼非常簡(jiǎn)單,把程序跑起來,點(diǎn)一下 click,果然界面卡住了,有點(diǎn)不可思議。

          2. 尋找死鎖原因

          接下來趕緊祭出 windbg 附加到進(jìn)程上一探究竟吧。

          1) 查看主線程

          界面無響應(yīng)了,自然是主線程卡住了,所以急需看一下此時(shí)的主線程在干嘛?用命令 ~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] 

          從堆棧輸出看,主線程最后是卡在 Task.Result 下的 Monitor.ObjWait 上,也就是說它還沒有取到最后的 jsonString,這就很奇怪了,都好幾分鐘了,難道網(wǎng)絡(luò)出問題啦 ? 我這網(wǎng)可是100M火力全開。。。??????

          2) jsonString 哪去了?

          判斷是不是網(wǎng)絡(luò)的問題,有一個(gè)好辦法,那就是直接暴力搜索托管堆,如果在托管堆上發(fā)現(xiàn)了 jsonString,那就說明是程序上的某些地方讓 Result 遲遲得不到結(jié)束,用命令 !dumpheap -type String -min 8500 +  !do 000001f19002fcf0 查看即可,如下圖所示:

          從圖中可以清晰的看出 html 回來了,既然都回來了,為啥還沒讓 Task.Result 結(jié)束呢?下一步就是看一看這個(gè) html 被誰持有,使用 !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+100000004d10dfe2b0
                      ->  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).

          從輸出結(jié)果看,這個(gè) System.String 最后被 5a10 線程的 WindowsFormsApp4.Form1 持有,可以用 !t 驗(yàn)證一下 5a10 到底是什么線程。


          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 712000001f1f1b2a270    2b220 Preemptive  0000000000000000:0000000000000000 000001f1f1ad5b90 0     MTA (Finalizer) 

          我去,5a10 竟然是主線程,真的有點(diǎn)混亂,主線程被卡死,string 又被主線程持有,完全是莫名其妙。

          3) 尋找突破點(diǎn)

          還是回過頭下冷靜思考下這條 引用鏈,我發(fā)現(xiàn)這里有一個(gè) Queue:-> 000001f18016c9c8 System.Collections.Queue,有思路了,我可以在入 Queue 的地方下個(gè) 斷點(diǎn)  來調(diào)試下源代碼,工具用 DnSpy, 說干就干。

          從圖中可以看到,當(dāng)前入Queue時(shí),用的是線程 10,也就是說此時(shí) string 還沒被主線程持有,再仔細(xì)分析下這個(gè)調(diào)用棧,我想你應(yīng)該就搞清楚了,反正我看完之后腦子中就有了這張圖。

          從圖中可以發(fā)現(xiàn),延續(xù)的 Task 最后被  WindowsFormsSynchronizationContext.Post 調(diào)度到了 Control 下的 Queue 中,而這 Queue 中的數(shù)據(jù)需要 UI線程 去執(zhí)行,所以就有了下面的對(duì)話:

          主線程: task小弟,你什么時(shí)候執(zhí)行完呀,我在等你信號(hào)呢?

          task: 老哥,我已在你家啦,你什么時(shí)候過來接我呀?

          總而言之:task需要主線程來執(zhí)行它,主線程卻在傻傻的等待 task 的 complete 狀態(tài),所以延續(xù)的task永遠(yuǎn)得不到執(zhí)行,這就出現(xiàn)了很尷尬的場(chǎng)面,不知道你明白了嗎? ??????

          三:破解之法

          知道了前因后果,這破解之法就簡(jiǎn)單了,大體上分兩種。

          1. 禁止將 延續(xù)task 丟到 Queue 中

          要切斷這條路,言外之意就是讓線程池自己結(jié)束這個(gè) task,這樣 UI線程 就能感知到這個(gè)task已完成,最終 UI線程 就能獲取最后的 html,做法就是在 await 后加上 ConfigureAwait(false) , 參考如下:

          2. 禁止阻塞主線程

          如果不阻塞主線程,那么主線程就可以自由的在 Control.Queue 中獲取需要執(zhí)行的任務(wù),改法也很簡(jiǎn)單,只需要在 GetJsonAsync 前加上 await 即可。

          三:總結(jié)

          結(jié)論就是多自己實(shí)操實(shí)操,理論知識(shí)是別人強(qiáng)制灌輸給你的,到底對(duì)還是不對(duì),其實(shí)你自己心里也沒底,實(shí)操驗(yàn)證才是真正屬于你的,而且也很難忘記,畢竟你曾今真的體驗(yàn)過,實(shí)操過,驗(yàn)證過。






          END


          往期精彩回顧




          【推薦】.NET Core開發(fā)實(shí)戰(zhàn)視頻課程 ★★★

          .NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門篇-開篇及總體規(guī)劃

          【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個(gè)接口多種實(shí)現(xiàn)的依賴注入與動(dòng)態(tài)選擇看這篇就夠了

          10個(gè)小技巧助您寫出高性能的ASP.NET Core代碼

          用abp vNext快速開發(fā)Quartz.NET定時(shí)任務(wù)管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

          關(guān)于C#異步編程你應(yīng)該了解的幾點(diǎn)建議

          C#異步編程看這篇就夠了


          瀏覽 61
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩在线三级 | 国产第一页精品先锋影音视频 | 成人18女人毛片 | 免费黄视频网站大全免费 | 久久精品10 |