日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一句 Task.Result 就死锁, 这代码还怎么写?

發布時間:2023/12/4 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一句 Task.Result 就死锁, 这代码还怎么写? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一:背景

1. 講故事

前些天把 .NET 高級調試 方面的文章索引到 https://github.com/ctripxchuang/dotnetfly 的過程中,發現了一個有意思的評論,截圖如下:

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

二:windbg 分析

1. 真的會死鎖嗎?

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

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;}}}

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

2. 尋找死鎖原因

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

1) 查看主線程

界面無響應了,自然是主線程卡住了,所以急需看一下此時的主線程在干嘛?用命令 ~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,這就很奇怪了,都好幾分鐘了,難道網絡出問題啦 ? 我這網可是100M火力全開。。。????????????

2) jsonString 哪去了?

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

從圖中可以清晰的看出 html 回來了,既然都回來了,為啥還沒讓 Task.Result 結束呢?下一步就是看一看這個 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+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.StringFound?1?unique?roots?(run?'!GCRoot?-all'?to?see?all?roots).

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

0:000>?!t????????????????????????????????????????????????????????????????????????????????????????????????????????Lock??ID?OSID?ThreadOBJ???????????State?GC?Mode?????GC?Alloc?Context??????????????????Domain???????????Count?Apt?Exception0????1?5a10?000001f1f1b01200??2026020?Preemptive??000001F1800E70E8:000001F1800E7FD0?000001f1f1ad5b90?0?????STA?2????2?712c?000001f1f1b2a270????2b220?Preemptive??0000000000000000:0000000000000000?000001f1f1ad5b90?0?????MTA?(Finalizer)?

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

3) 尋找突破點

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

從圖中可以看到,當前入Queue時,用的是線程 10,也就是說此時 string 還沒被主線程持有,再仔細分析下這個調用棧,我想你應該就搞清楚了,反正我看完之后腦子中就有了這張圖。

從圖中可以發現,延續的 Task 最后被 ?WindowsFormsSynchronizationContext.Post 調度到了 Control 下的 Queue 中,而這 Queue 中的數據需要 UI線程 去執行,所以就有了下面的對話:

主線程: task小弟,你什么時候執行完呀,我在等你信號呢?

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

總而言之:task需要主線程來執行它,主線程卻在傻傻的等待 task 的 complete 狀態,所以延續的task永遠得不到執行,這就出現了很尷尬的場面,不知道你明白了嗎? ????????????

三:破解之法

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

1. 禁止將 延續task 丟到 Queue 中

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

2. 禁止阻塞主線程

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

三:總結

結論就是多自己實操實操,理論知識是別人強制灌輸給你的,到底對還是不對,其實你自己心里也沒底,實操驗證才是真正屬于你的,而且也很難忘記,畢竟你曾今真的體驗過,實操過,驗證過。

END

工作中的你,是否已遇到 ...?

1. CPU爆高

2. 內存暴漲

3. 資源泄漏

4. 崩潰死鎖

5. 程序呆滯

等緊急事件,全公司都指望著你能解決...? 危難時刻才能展現你的技術價值,作為專注于.NET高級調試的技術博主,歡迎微信搜索: 一線碼農聊技術,免費協助你分析Dump文件,希望我能將你的踩坑經驗分享給更多的人。

總結

以上是生活随笔為你收集整理的一句 Task.Result 就死锁, 这代码还怎么写?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。