一个超经典 WinForm 卡死问题的最后一次反思
一:背景
1. 讲故事
在我分析的 200+ dump 中,同样会遵循着 28原则,总有那些经典问题总是反复的出现,有很多的朋友就是看了这篇 一个超经典 WinForm 卡死问题的再反思 找到我,说 WinDbg 拦截 System_Windows_Forms_ni System.Windows.Forms.Application+MarshalingControl..ctor 总会有各种各样的问题,而且 windbg 也具有强侵入性,它的附加进程方式让很多朋友望而生畏!
这一篇我们再做一次反思,就是如何不通过 WinDbg 找到那个 非主线程创建的控件,那到底用什么工具的? 对,就是用 Perfview 的墙钟模式。
二:Perview 的墙钟调查
1. 测试案例
我还是用上一篇提到的案例,用 backgroundWorker1 的工作线程去创建一个 Button 控件来模拟这种现象,参考代码如下:
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click_1(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork_1(object sender, DoWorkEventArgs e)
{
Button btn = new Button();
var query = btn.Handle;
}
}
}
一旦控件在工作线程上被创建,代码内部就会实例化 MarshalingControl 和 WindowsFormsSynchronizationContext,这里就用前者来探究。
2. 寻找 MarshalingControl 调用栈
那怎么去寻找这个调用栈呢?在 perfview 中有一个 Thread Time 复选框,它可以记录到 Thread 的活动轨迹,在活动轨迹中寻找我们的目标类 MarshalingControl 即可,有了思路之后说干就干,命令行下的参考代码:
PerfView.exe "/DataFile:PerfViewData.etl" /BufferSizeMB:256 /StackCompression /CircularMB:500 /KernelEvents:ThreadTime /NoGui /NoNGenRundown collect

当然也可以在 Focus process 中输入你的进程名来减少 Size,启动 prefview 监控之后,我们打开程序,点击 Button 按钮之后,停止 Prefview 监控,稍等片刻之后我们打开 Thread Time Stacks,检索我们要的 MarshalingControl 类, 截图如下:

从卦中可以看到如下三点信息:
- 当前 prefview 录制了 34.7s
- MarshalingControl.ctor 有 2 个实例
- 二次实例化分别在 22.84s 和 24.12s
接下来可以右键选择 Goto -> Goto Item in Callers 看一下它的 Callers 到底都是谁?截图如下:

从卦中可以清晰的看到如下信息:
第一个实例是由 System.Windows.Forms.ScrollableControl..ctor() 触发的。
第二个实例是由 System.Windows.Forms.ButtonBase..ctor() 触发的。
大家可以逐一的去探究,第一个实例是窗体自身的 System.Windows.Forms.Form ,后者就是那个罪魁祸首,卦中信息非常清楚指示了来自于 WindowsFormsApp2.Form1.backgroundWorker1_DoWork_1,是不是非常的有意思?
3. 如何让窗体尽可能早的卡死
所谓的尽早卡死就是尽可能早的让主线程出现如下调用栈。
0:000:x86> !clrstack
OS Thread Id: 0x4eb688 (0)
Child SP IP Call Site
002fed38 0000002b [HelperMethodFrame_1OBJ: 002fed38] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
002fee1c 5cddad21 System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)
002fee34 5cddace8 System.Threading.WaitHandle.WaitOne(Int32, Boolean)
002fee48 538d876c System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
002fee88 53c5214a System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
002fee8c 538dab4b [InlinedCallFrame: 002fee8c]
002fef14 538dab4b System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
002fef48 53b03bc6 System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback, System.Object)
002fef60 5c774708 Microsoft.Win32.SystemEvents+SystemEventInvokeInfo.Invoke(Boolean, System.Object[])
002fef94 5c6616ec Microsoft.Win32.SystemEvents.RaiseEvent(Boolean, System.Object, System.Object[])
002fefe8 5c660cd4 Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(Int32, IntPtr, IntPtr)
002ff008 5c882c98 Microsoft.Win32.SystemEvents.WindowProc(IntPtr, Int32, IntPtr, IntPtr)
...
如果不能尽早的让程序卡死,那你就非常被动,因为在真实的案例实践中,这个 t1 时间的 new button,可能在 t10 时间因为某些操作才会出现程序卡死,所以你会被迫用 prefview 一直监视,而一直监视就会导致生成太多的 etw 事件,总之很搞的。
先感谢下上海的包老师 提供的一段很棒的脚本,也经过了老师实测
让这个问题解决起来更加完美
这里我用 ILSpy 反编译一下这个执行程序,完整代码如下:
// Freezer.FreezerForm
using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Freezer;
public class FreezerForm : Form
{
private Button btnFreezeEm;
private Container components = null;
private const uint WM_SETTINGCHANGE = 26u;
private const uint HWND_BROADCAST = 65535u;
private const uint SMTO_ABORTIFHUNG = 2u;
public FreezerForm()
{
InitializeComponent();
}
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
btnFreezeEm = new System.Windows.Forms.Button();
SuspendLayout();
btnFreezeEm.Location = new System.Drawing.Point(89, 122);
btnFreezeEm.Name = "btnFreezeEm";
btnFreezeEm.Size = new System.Drawing.Size(115, 23);
btnFreezeEm.TabIndex = 0;
btnFreezeEm.Text = "Freeze 'em!";
btnFreezeEm.Click += new System.EventHandler(btnFreezeEm_Click);
AutoScaleBaseSize = new System.Drawing.Size(6, 15);
base.ClientSize = new System.Drawing.Size(292, 267);
base.Controls.Add(btnFreezeEm);
base.Name = "FreezerForm";
Text = "Freezer";
ResumeLayout(false);
}
[DllImport("user32.dll")]
private static extern uint SendMessageTimeout(uint hWnd, uint msg, uint wParam, string lParam, uint flags, uint timeout, out uint result);
[STAThread]
private static void Main()
{
Application.Run(new FreezerForm());
}
private void btnFreezeEm_Click(object sender, EventArgs e)
{
try
{
Cursor = Cursors.WaitCursor;
SendMessageTimeout(65535u, 26u, 0u, "Whatever", 2u, 5000u, out var _);
}
finally
{
Cursor = Cursors.Arrow;
}
}
}
这个脚本供大家参考吧,这里要提醒一下,我实测了下需要在运行时需要反复点以及最小最大话可能会遇到一次,不管怎么说还是非常好的宝贵资料。
三:总结
关于对 非主线程创建控件 的问题,这已经是第三篇思考了,希望后续不要再写这个主题了。

一个超经典 WinForm 卡死问题的最后一次反思的更多相关文章
- 一个超经典 WinForm 卡死问题的再反思
一:背景 1.讲故事 这篇文章起源于昨天的一位朋友发给我的dump文件,说它的程序出现了卡死,看了下程序的主线程栈,居然又碰到了 OnUserPreferenceChanged 导致的挂死问题,真的是 ...
- 网络采集软件核心技术剖析系列(7)---如何使用C#语言搭建程序框架(经典Winform界面,顶部菜单栏,工具栏,左边树形列表,右边多Tab界面)
一 本系列随笔概览及产生的背景 自己开发的豆约翰博客备份专家软件工具问世3年多以来,深受广大博客写作和阅读爱好者的喜爱.同时也不乏一些技术爱好者咨询我,这个软件里面各种实用的功能是如何实现的. 该软件 ...
- 腾讯出品的一个超棒的 Android UI 库
腾讯出品的一个超棒的 Android UI 库 相信做 Android 久了大家都会有种体会,那就是 Android 开发相对于前端开发来说统一的 UI 开源库比较少.造成这种现象的原因一方面是大多数 ...
- scala 入门Eclipse环境搭建及第一个入门经典程序HelloWorld
scala 入门Eclipse环境搭建及第一个入门经典程序HelloWorld 学习了: http://blog.csdn.net/wangmuming/article/details/3407911 ...
- 搭建一个超好用的 cmdb 系统
10 分钟为你搭建一个超好用的 cmdb 系统 CMDB 是什么,作为 IT 工程师的你想必已经听说过了,或者已经烂熟了,容我再介绍一下,以防有读者还不知道.CMDB 的全称是 Configurati ...
- 吴裕雄 Bootstrap 前端框架开发——Bootstrap 按钮:制作一个超小按钮
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- 【转载】制作一个超精简的WIN7.gho
首先说明一点,这个Resource不是我制作的,Google搜了下GHO镜像文件制作,挺复杂的.如果要从头到尾自己制作GHO文件可以参考: http://baike.so.com/doc/674790 ...
- 王家林 大数据Spark超经典视频链接全集[转]
压缩过的大数据Spark蘑菇云行动前置课程视频百度云分享链接 链接:http://pan.baidu.com/s/1cFqjQu SCALA专辑 Scala深入浅出经典视频 链接:http://pan ...
- 分享一个客户端程序(winform)自动升级程序,思路+说明+源码
做winform的程序,不管用没用过自动更新,至少都想过自动更新是怎么实现的. 我这里共享一个自动更新的一套版本,给还没下手开始写的人一些帮助,也希望有大神来到,给指点优化意见. 本初我是通过sock ...
- 打造支持apk下载和html5缓存的 IIS(配合一个超简单的android APP使用)具体解释
为什么要做这个看起来不靠谱的东西呢? 由于刚学android开发,还不能非常好的熟练控制android界面的编辑和操作,所以我的一个急着要的运用就改为html5版本号了,反正这个运用也是须要从serv ...
随机推荐
- Django4全栈进阶之路1 Django4下载与安装
python 下载安装: 下载网址:https://www.python.org/downloads/ 安装方法:https://www.cnblogs.com/beichengshiqiao/p/1 ...
- Error: Cannot find module ‘webpack-cli/bin/config-yargs‘
今配置一个webpack-dev-server进行服务端渲染时老是不正确 npm run dev 就崩了 起初的配置为 "devDependencies": { "web ...
- openlayers 使用canvas绘制圆形头像图标
记录一个使用canvas 将一张图片等比缩放,裁剪为一个圆 1.原始图片 2.绘制后在地图中呈现的样式 3.设置样式的函数 /** * 设置Style */ setStyleOnPersonLocat ...
- \n被当成回车处理
Regex.Escape C# 字符串变量str 的值为"a\nb"如果直接输出显示的话,就成了:ab需要输出显示为:a\nb string str = "a\nb& ...
- RStuido Server 选择不同的 R 版本(conda 中的不同 R 版本)
自从上一次服务器重装系统之后,总感觉缺少了一些东西,安装R包很多依赖库报错,也可以解决,但总是存在,烦. 一天,一个同事问我说ggpubr包安装不成功,我就自己试了一下,真的是--安装不成功. 当你到 ...
- 一次 HPC 病毒感染与解决经历
周一的时候,有同事反馈说,HPC 的项目报告路径正在不断产生 *.exe 和 *.pif 文件,怀疑是不是被病毒感染! 收到信息,第一时间进去目录,的确发现该目录每个几秒钟就自动生成一个 *.exe ...
- Nature 重大发现:癌基因竟不在染色体上?第一作者吴思涵亲身解读!
编辑|李丽 记者|布德鸟 图片提供|吴思涵 今日凌晨,美国加州大学圣地亚哥分校 Ludwig 癌症研究所的 Paul Mischel 教授领导的研究团队发现, 大量的癌基因并不在染色体上,而是会从染色 ...
- STL-queue(ACM)
重构函数(默认) queue<int> q; 基本操作 q.front(); // 队列最前面的元素q.back(); // 队列最后面的元素q.size(); // 返回队列长度q.em ...
- NameError: name 'List' is not defined
当在python出现该问题是,使用from typing import List.
- 识别一切模型RAM(Recognize Anything Model)及其前身 Tag2Text 论文解读
总览 大家好,我是卷了又没卷,薛定谔的卷的AI算法工程师「陈城南」~ 担任某大厂的算法工程师,带来最新的前沿AI知识和工具,欢迎大家交流~ 继MetaAI 的 SAM后,OPPO 研究院发布识别一切模 ...