一句 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文件,希望我能将你的踩坑经验分享给更多的人。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/302506.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ant 改变表格数据_表格技巧—Excel表格怎么替换数字

在编制报表时&#xff0c;如果把序列号中的某些数字改变&#xff0c;一个个更正&#xff0c;肯定是比重新录一遍还要慢的&#xff0c;如果只是想替换其中的数字&#xff0c;其实可以利用Excel表格自带的查找替换功能&#xff0c;一步到位解决。接下来小编教大家怎么样将一大批的…

程序员的鄙视链

全世界有3.14 % 的人已经关注了数据与算法之美最近这几年在世界各地突然吹起了一股全民写程序的风潮&#xff0c;连即将卸任的美国总统奥巴马都在写 JavaScript 了&#xff0c;但是身为一介靠写程序&#xff08;以及在上班时间胡乱上网&#xff09;来谋生的 developer&#xff…

基于 registry 搭建 Docker 私有镜像仓库

dockerhub: https://registry.hub.docker.com/_/registry安装命令docker run -p 5000:5000 -d -v /opt/registry:/opt/registry --restart always --name myregistry registry参数介绍&#xff1a;•-p 容器的端口映射&#xff0c;这里绑定 5000•-d 后台运行容器•-v 挂载主机…

BBSXP论坛手工得到用户md5密码的方法

07年发表在《***手册》的一篇文章。通过阅读本文&#xff0c;您将了解到&#xff1a;1、一种手工得到两个版本的BBSXP论坛的用户md5密码的方法。2、如何Cookie欺骗。大家都知道BBSXP论坛5.13&#xff0c;5.15版本的blog.asp文件存在注入漏洞&#xff0c;网上也有几种这个漏洞的…

2010C语言添加背景图片_2019级C语言大作业 - 火柴人试炼之地

C语言大作业 - 火柴人试炼之地https://www.zhihu.com/video/1200491618794606592分享19级同学大一上学期用C语言实现的火柴人试炼之地。分步骤代码、图片音乐素材、可执行程序可以从百度网盘下载&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1XelHD8GVnXfR8coAYOAC9w …

基于单TCP连接的高吞吐模型设计

对于服务与服务之间往往需要高效的吞吐的信息交互&#xff0c;但在绝大部分服务应用中为了实现高吞吐交互都是基于连接池模式&#xff0c;即通过多个TCP连接来提高吞吐量&#xff0c;这种设计完全是通过增加IO的读写量来实现高效吞吐。如果能减少连数和降低IO量&#xff08;合并…

程序员8大终极杀器,你get了几个?

全世界有3.14 % 的人已经关注了数据与算法之美经常有同学问&#xff1a;作为程序员&#xff0c;终极杀器是什么&#xff1f;其实有大佬早已回答过&#xff1a;持续学习&#xff0c;开阔视野才是程序员的终极杀器&#xff01;基于此&#xff0c;给大家一个建议&#xff1a;不妨多…

winserver2016 401您无权使用所提供的凭据查看此目录或页面_不用找了,30分钟帮你搞定使用 Spring Cloud 和 Docker 轻松构建微服务架构!...

点击上方[全栈开发者社区]→右上角[...]→[设为星标⭐]【编者的话】如何使用Spring Boot、Spring Cloud、Docker和Netflix的一些开源工具来构建一个微服务架构。本文通过使用Spring Boot、Spring Cloud和Docker构建的概念型应用示例&#xff0c;提供了了解常见的微服务架构模式…

女朋友掉水里,各类程序猿怎么救?

全世界有3.14 % 的人已经关注了数据与算法之美前天发了一篇有趣的文章《如果辅导员掉进水里&#xff0c;各个专业的学生将会如何解救&#xff1f;》那么&#xff0c;如果程序猿的女朋友落水了&#xff0c;他们会怎么救呢&#xff1f;不会像在校的计算机学院的学生那样“找跟网线…

联机分析的列式数据库 clickHouse

ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。在传统的行式数据库系统中&#xff0c;数据按如下顺序存储&#xff1a;RowWatchIDJavaEnableTitleGoodEventEventTime#0893543506621Investor Relations12016-05-18 05:19:20#1903295099580Contact us12016-05-…

网管小王的工具包

我是一名网管&#xff0c;工作主要是维护单位的网络线路和电话线路&#xff0c;今天闲来没事&#xff0c;把工具包中的工具show一下吧。笔记本电脑打线器&#xff0c;用来维修模块和打线用的两根长短不同的网线和一根电话线螺丝刀&#xff0c;什么扁口&#xff0c;十字花&#…

.NET之生成数据库全流程

开篇语本文主要是回顾下从项目创建到生成数据到数据库(代码优先)的全部过程。采用EFCore作为ORM框架。本次示例环境&#xff1a;vs2019、net5、mysql创建项目本次事例代码是用过vs2019创建的ASP.NET Core Web API项目可以通过可视化界面创建或者通过命令行创建dotnet new webap…

进军人工智能,数学基础很重要?

随着科技的快速发展&#xff0c;人工智能的重要性日渐显现。对于大多数新手来说&#xff0c;弄清楚入门人工智能需要哪些数学基础、需要熟悉什么框架等&#xff0c;都至关重要。机器学习是一个异常丰富的研究领域&#xff0c;有大量未解决的问题&#xff1a;公正、可解释性、易…

修改正文中参考文献标注_论文写作中怎样正确插入参考文献,引用文献如何标注?...

论文写作中怎样正确插入引文文献&#xff0c;引用文献如何标注&#xff1f;不管是大学毕业生还是期刊/评职称的我们在面对撰写论文时&#xff0c;参考文献的引用是必不可少的。参考文献的引用可以给论文增添很多的光彩。正确的在论文中引用参考问下你会在论文编写的同时省去很大…

.Net之多语言配置

开篇语首先非常感谢各位朋友或技术爱好者的关注。介绍支持多语言使网站可以覆盖更广泛的受众。ASP.NET Core 提供的服务和中间件可将网站本地化为不同的语言。本次示例环境&#xff1a;vs2019、net5配置无需引用Nuget包即可实现以下功能。注入容器services.AddLocalization(t &…

21张GIF动图让你秒懂数学原理

全世界有3.14 % 的人已经关注了数据与算法之美数学是很难的科学&#xff0c;但因为它是科学家用数学来解释宇宙的语言&#xff0c;我们无可避免的要学习它。看看下面的这些GIF动图&#xff0c;它们提供了视觉的方式来帮助你理解各种数学技巧。推荐阅读《12堂魔力数学课》。1.椭…

表格过滤器_记录和管理零散信息,什么软件比 Excel 表格更方便

SeaTable 的目标是帮助大家更好的组织和管理各种零散的信息&#xff0c;团队的请假信息就属于这样一类零散的信息。下面我们来看一下 &#xff0c;SeaTable 相比于 Excel 是如何更好的帮助我们来组织和管理这样的信息的。用 Excel 记录的团队请假信息下面两张表是比较常见的请假…

使用 Source Generator 代替 T4 动态生成代码

使用 Source Generator 代替 T4 动态生成代码Intro在 Source Generator 出现之前有一些重复性的代码&#xff0c;我会使用 T4 去生成&#xff0c;这样就可以一定程度上避免复制粘贴和可维护性也会更好一些。在了解了一些 Source Generator 之后&#xff0c;就想尝试把现在项目里…

资料分享 | 数据挖掘实例资料分享来袭

小编从大学开始&#xff0c;便开启资料收集功能。随着大数据时代的来临&#xff0c;计算机发展进入新的阶段&#xff0c;再加上日常的深入研究&#xff0c;小编收集整理了丰富的数据挖掘资料&#xff0c;内容涵盖“程序”&#xff0c;“数据”、“文档”等。这次小编再次把所有…

修改图层的范围_【PS|第39期】数字绘画 使用填充图层

惟有悲观净化而成的乐观&#xff0c;才是真正的乐观。——尼采)填充图层是一种只承载纯色、渐变和图案的特殊图层&#xff0c;其特点是填充内容可以修改。另外&#xff0c;设置成不同的混合模式和不透明度后&#xff0c;可用于修改其他图层的颜色或生成图像混合效果。填充图层都…