UI僵死分析
原因剖析
UI僵死无非只是因为UI线程因繁忙而无法去接受用户的响应。详细说来内在原因有以下两个:
- 正常的业务代码写在UI线程中执行,业务代码的任务繁重导致UI线程无法分身去接受用户的界面输入
- UI控件在非UI线程中创建。原因如下如述:
- 每一个UI控件创建后都向SystemEvents注册UserPreferenceChanged事件,并且创建了控件的线程会被自动安装WindowsFormsSynchronizationContext作为其同步上下文
- 系统默认在UI线程里创建一个隐藏窗口“.NET-BroadcastEventWindow”来获取SystemEvents相关的系统消息
- 此隐藏窗口获取到消息后通过系统的PostMessage方法向注册了此事件的各UI控件发送通知并等待(注意不是通过SendMessage)
- 若某UI控件未创建在UI线程上,因为其创建控件的线程不会监视和获取本线程的消息队列中的消息,所以UI线程的PostMessage方法会一直等待,UI呈现僵死状态
PostMessage
- 将消息送至目标window所在线程(可通过系统API获取控件的句柄所属的线程)的“Posted Message Queue”消息队列,消息称为列队型(queued)型消息
- Control.Invoke与Control.BeginInvoke都调用PostMessage(相比SendMessage可防止死锁),区别是前者会使用WaitForWaitHandle来等待消息处理完毕
SendMessage
- 将消息送至目标window所在线程的“Sent Message Queue”消息队列,但消息称为非列队型(Non-queued)消息
- 发送线程调用SendMessage后会挂起并等待返回,如果期间有其他线程发消息给这个发送线程,它可以响应,但仅限于非队列型(Non-queued)消息
- WH_CALLWNDPROC钩子用于监视SendMessage调用
异常发生后如何诊断
诊断的目的是要确定引发了UI线程繁忙的原因。
- 若是因为上述第1点原因,即因为正常的业务代码在UI线程中跑的话,直接用VS等调试工具看一下UI线程的堆栈即可;
- 若若因为上述第2点原因,即因为在非UI线程中创建了UI控件的话,那得先找出此控件。UI线程里此控件因为触发了SynchronizationContext.Send方法而冻结。
- 使用spy++可以直接查看活动的后台线程上是否有控件。不过若线程将控件创建出来放在堆内存上后线程就消亡了的话,那就无法看到了。
- 使用Windbg:
- 获取UI线程堆栈一看便知控件的类名,对照着Windbg的“!dso”命令结果找到此控件的地址,再查找其引用。若UI线程中显示的控件类型因其为内部的子控件且为通用类型而无法直接定位代码的话,那么可以尝试追溯找到此控件的父控件。
- 也可获取让UI冻结的WindowsFormSynchronizationContext,再通过以下方式找出目标托管线程的ID。不过因为托管线程在消亡后的ID可以被重复使用,所以通过此方式找到的托管线程ID可能是已经消亡的线程的ID,所以之后要找到目标Thread对象再比较其Thread.m_ExecutionContext._syncContext是否不为空且正为让UI冻结的WindowsFormSynchronizationContext。
- 使用“!do <synchronizationContext对象地址>”命令显示其数据结构,从成员destinationThreadRef获取指定了创建控件的目标线程的WeakReference对象地址
- 使用“!dumpobject <WeakReference对象地址>”显示其数据结构,从成员m_handle获取创建控件的目标线程的句柄
- 使用“dd <目标线程的句柄> L1”命令显示此句柄中包含的线程地址
- 使用“!dumpobject <目标线程地址>”命令显示目标线程的数据结构,从成员m_ManagedThreadId获取目标线程的托管线程号
- 使用“?0n<托管线程号>”命令获取托管线程号的16进制数
防患于未然
写代码时应该遵循这条原则:保证线程安全,尤其是不该在非UI线程上直接进行UI操作,包括控件的创建。
不过团队水平参差不齐,即使是一些老手也难免犯错。
所以如果能够拦截控件创建的过程,那么就可以通过Windows API根据此控件的句柄获取其在运行的线程号,看是否就是主UI线程号来输出日志,以在调试阶段解决问题。
有以下两种途径:
拦截Winodws Message。通过创建Global Hook拦截所有线程的窗口创建消息。
拦截Windows API。通过拦截各线程对窗口创建的API的调用。
使用windbg拉截windows api的调用前不要忘了为其加载符号。如:srv*c:\symbols*http://msdl.microsoft.com/download/symbols
本人通过EasyHook开源库使用了第2种方法,即拦截对WindowsAPI的调用完成了工具的创建,截图如下:

参考
Debugging Windows Forms Application Hangs During SystemEvents.UserPreferenceChanged
Windows Forms application freezes when system settings are changed or the workstation is locked
Mysterious Hang or The Great Deception of InvokeRequired
WinForm二三事(三)Control.Invoke&Control.BeginInvoke
一千个是什么 - Windows消息机制(Windows Messaging)
Using Window Messages to Implement Global System Hooks in C#
Windows下Hook API技术 inline hook
Change C# Class object to System.IntPtr
UI僵死分析的更多相关文章
- 对石家庄铁道大学网站UI的分析
作为我们团队的PM,老师对我们提出了一些额外的要求,所以我发表这篇博客来谈一下对石家庄铁道大学网站UI的分析. 首先,PM 对项目所有功能的把握, 特别是UI.最差的UI, 体现了 ...
- 针对某一网站的UI进行分析
本周课上教学通过对PM(项目经理)的学习,我了解到PM 对项目所有功能的把握, 特别是有关的UI内容.最差的UI, 体现了团队的组织架构:其次, 体现了产品的内部结构:最好, 体现了用户的自然需求. ...
- Android recovery UI实现分析
Android recovery模式为何物? 关于这个问题, baidu上已经有无数的答案.不理解的朋友先补习一下. 从纯技术角度来讲, recovery和android本质上是两个独立的rootfs ...
- Android 性能优化(2)性能工具之「Hierarchy Viewer 」Optimizing Your UI:分析哪个view有性能问题,查看屏幕上某像素点的坐标,颜色等
Optimizing Your UI In this document Using Hierarchy Viewer Running Hierarchy Viewer and choosing a w ...
- Reveal UI 分析工具分析手机 App
上篇文章介绍了: Reveal UI 分析工具简单使用 这里介绍如何使用 Reveal UI 分析工具来进行手机 App UI 界面的分析. 前提准备: (1)已安装 Reveal 的 Mac (2) ...
- 对石家庄铁道大学网站首页进行UI分析
对石家庄铁道大学网站首页进行UI界面分析首先,铁道大学的网页首页分为图文热点,学校新闻,校内公告,媒体看铁大,学术咨询等等模块.通过分析这些模块,可以看出,学校网站首页针对的使用对象有很多,包括学校领 ...
- BookStore示例项目---菜单栏UI分析
部署 参照 ABP示例项目BookStore搭建部署 项目解构 1).动态脚本代理 启动项目时,默认会调用两个接口 /Abp/ApplicationConfigurationScript /Abp/S ...
- 转——Android应用开发性能优化完全分析
[工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.] 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉 ...
- DIY Ruby CPU 分析——Part I
[编者按]原文作者 Emil Soman,Rubyist,除此之外竟然同时也是艺术家,吉他手,Garden City RubyConf 组织者.本文是DIY Ruby CPU Profiling 的第 ...
随机推荐
- 怎样使用CMenu类
CMenu类从CObject类派生而来.为什么要使用CMenu类呢?AppWzard不是把菜单做好了吗?在资源编辑器上修改菜单不是很方便吗? 我是个vc++初学者,自从当斑竹以来,天天看贴子, ...
- 设置Oracle用IP远程连接和客户端访问
要想将oracle设置为多用户可远程访问,须进行以下设置: 1.路径:D:\app\Administrator\product\11.1.0\db_1\NETWORK\ADMIN\listener.o ...
- JavaScript权威指南科20章 client记忆
20 client记忆 client几种形式存储的: web记忆 cookie IE userData 离线应用 web数据库 文件系统api 20.1 localStorage 和 sessionS ...
- Android Fragement学习笔记(三)----PreferenceFragment的使用
相信大家对Perference都比較熟悉了,也就是我们常说的偏好设置,首选项设置,能够保存一些数据,比如我们在上一次使用的时候的一些内容,希望在下一次启动后依旧生效,而不须要再进行配置那么麻烦.一般这 ...
- 使用Delphi声明C++带函数的结构体实战 good
在小组开发中,应用程序部分采用Delphi7,一些组件使用C++做.在今天将一个动态库的C++接口声明头文件转换为D7的Unit单元时,一切都很顺利,直到遇到下面这样一个另类的东西: typedef ...
- 如何隐藏 QLPreviewController 的 Action 按钮?
在 iOS 6 以前,可以在 present QLPreviewController 之后使用以下代码: [previewController.navigationItem setRightBarBu ...
- HDU 3911 Black And White 分段树 题解
Problem Description There are a bunch of stones on the beach; Stone color is white or black. Little ...
- hdu 2594 Simpsons’ Hidden Talents 【KMP】
题目链接:http://acm.acmcoder.com/showproblem.php?pid=2594 题意:求最长的串 同一时候是s1的前缀又是s2的后缀.输出子串和长度. 思路:kmp 代码: ...
- ZOJ 3542 2011大连现场赛D题(简单模拟)
Hexadecimal View Time Limit: 2 Seconds Memory Limit: 65536 KB Hexadecimal is very important an ...
- Python 分析Twitter用户喜爱的推文
CODE: #!/usr/bin/python # -*- coding: utf-8 -*- ''' Created on 2014-8-5 @author: guaguastd @name: an ...