原因剖析

UI僵死无非只是因为UI线程因繁忙而无法去接受用户的响应。详细说来内在原因有以下两个:

  1. 正常的业务代码写在UI线程中执行,业务代码的任务繁重导致UI线程无法分身去接受用户的界面输入
  2. UI控件在非UI线程中创建。原因如下如述:
    1. 每一个UI控件创建后都向SystemEvents注册UserPreferenceChanged事件,并且创建了控件的线程会被自动安装WindowsFormsSynchronizationContext作为其同步上下文
    2. 系统默认在UI线程里创建一个隐藏窗口“.NET-BroadcastEventWindow”来获取SystemEvents相关的系统消息
    3. 此隐藏窗口获取到消息后通过系统的PostMessage方法向注册了此事件的各UI控件发送通知并等待(注意不是通过SendMessage)
    4. 若某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。
      1. 使用“!do <synchronizationContext对象地址>”命令显示其数据结构,从成员destinationThreadRef获取指定了创建控件的目标线程的WeakReference对象地址
      2. 使用“!dumpobject <WeakReference对象地址>”显示其数据结构,从成员m_handle获取创建控件的目标线程的句柄
      3. 使用“dd <目标线程的句柄> L1”命令显示此句柄中包含的线程地址
      4. 使用“!dumpobject <目标线程地址>”命令显示目标线程的数据结构,从成员m_ManagedThreadId获取目标线程的托管线程号
      5. 使用“?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

细说UI线程和Windows消息队列

理解Windows窗体和WPF中的跨线程调用

WinForm二三事(三)Control.Invoke&Control.BeginInvoke

一千个是什么 - Windows消息机制(Windows Messaging)

Invoke and BeginInvoke

Windows 应用程序交互过程

PostMessage与SendMessage

Windows 应用程序交互过程

Using Window Messages to Implement Global System Hooks in C#

PInvoke.net

Windows API函数大全

Deviare API Hook Overview

EasyHook

HOOK API 函数跳转详解

Windows下Hook API技术 inline hook

Change C# Class object to System.IntPtr

GCHandle.Alloc 方法 (Object)

如何获得指定进程的主窗口

EnumWindows function

Control.InvokeRequired 属性

线程句柄

UI僵死分析的更多相关文章

  1. 对石家庄铁道大学网站UI的分析

         作为我们团队的PM,老师对我们提出了一些额外的要求,所以我发表这篇博客来谈一下对石家庄铁道大学网站UI的分析.      首先,PM 对项目所有功能的把握, 特别是UI.最差的UI, 体现了 ...

  2. 针对某一网站的UI进行分析

    本周课上教学通过对PM(项目经理)的学习,我了解到PM 对项目所有功能的把握, 特别是有关的UI内容.最差的UI, 体现了团队的组织架构:其次, 体现了产品的内部结构:最好, 体现了用户的自然需求. ...

  3. Android recovery UI实现分析

    Android recovery模式为何物? 关于这个问题, baidu上已经有无数的答案.不理解的朋友先补习一下. 从纯技术角度来讲, recovery和android本质上是两个独立的rootfs ...

  4. Android 性能优化(2)性能工具之「Hierarchy Viewer 」Optimizing Your UI:分析哪个view有性能问题,查看屏幕上某像素点的坐标,颜色等

    Optimizing Your UI In this document Using Hierarchy Viewer Running Hierarchy Viewer and choosing a w ...

  5. Reveal UI 分析工具分析手机 App

    上篇文章介绍了: Reveal UI 分析工具简单使用 这里介绍如何使用 Reveal UI 分析工具来进行手机 App UI 界面的分析. 前提准备: (1)已安装 Reveal 的 Mac (2) ...

  6. 对石家庄铁道大学网站首页进行UI分析

    对石家庄铁道大学网站首页进行UI界面分析首先,铁道大学的网页首页分为图文热点,学校新闻,校内公告,媒体看铁大,学术咨询等等模块.通过分析这些模块,可以看出,学校网站首页针对的使用对象有很多,包括学校领 ...

  7. BookStore示例项目---菜单栏UI分析

    部署 参照 ABP示例项目BookStore搭建部署 项目解构 1).动态脚本代理 启动项目时,默认会调用两个接口 /Abp/ApplicationConfigurationScript /Abp/S ...

  8. 转——Android应用开发性能优化完全分析

    [工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.] 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉 ...

  9. DIY Ruby CPU 分析——Part I

    [编者按]原文作者 Emil Soman,Rubyist,除此之外竟然同时也是艺术家,吉他手,Garden City RubyConf 组织者.本文是DIY Ruby CPU Profiling 的第 ...

随机推荐

  1. golang使用pprof检查goroutine泄露

    有一段时间,我们的推送服务socket占用非常不正常,我们自己统计的同一时候在线就10w的用户,可是占用的socket居然达到30w,然后查看goroutine的数量,发现已经60w+. 每一个用户占 ...

  2. 【android】禁止Edittext弹出软键盘而且使光标正常显示

    /** * 禁止Edittext弹出软件盘,光标依旧正常显示. */ public void disableShowSoftInput() { if (android.os.Build.VERSION ...

  3. java图形

    JFreeCharteclipse图形化编程插件jigloojfaceibm的jface基于swt,swing解决了awt存在的lcd问题.swing组件:container,window,frame ...

  4. ios学习:AVAudioPlayer播放音乐文件及读取ipod库中的音乐文件

    首先要导入AVFoundation框架及 #import <AVFoundation/AVFoundation.h>头文件 注意:要在真机上调试 下面是ipad上的调试效果 下面是代码,代 ...

  5. EA强大功能之代码凝视

    前面讲了EA怎样方便我们生成代码,这次讲一下,怎样生成具体的凝视. 1.文件表头凝视 (1)点击工具----选项 在常规项里改动作者: 在代码project中改动代码project的默认语言. (2) ...

  6. JavaScript2谁刚开始学习应该知道4最佳实践文章(翻译)

    原版的:24 JavaScript Best Practices for Beginners (注:阅读原文的时候没有注意公布日期,觉得不错就翻译了,翻译到JSON.parse那一节觉得有点不正确路才 ...

  7. hdu4280(最大流)

    传送门:Island Transport 题意:有N个岛屿 M条无向路 每个路有一最大允许的客流量,求从最西的那个岛屿最多能运用多少乘客到最东的那个岛屿. 分析:无向图正反都加弧,权值一样,这题点多, ...

  8. uva 1434 - YAPTCHA(数论)

    题目链接:uva 1434 - YAPTCHA 题目大意:给定n和k,求题目中给定的式子S(n). 解题思路:威尔逊定理,x为素数时有,((x−1)!+1)%x==0,所以对于本题.假设3*k+7为素 ...

  9. Invalid character constant

    Invalid character constant 无效的字符常数 可能是双引号写成了单引号了.

  10. CSS实现输入框的高亮效果-------Day50

    又到周末了,这一天天过的真快,明天应该回老家了.不知道会不会有机会进行编写.尽量争取吧,实在不想就这样间断.假设说从前会一天天无聊到爆,那如今自己应该是一天天忙的要死,欠缺了太多东西,那些浪费的时间可 ...