序言:
最近在跟踪一些C#程序上死机的Bug,市场报上来也只有一些简单信息,错误信息比较少,查了很MSDN很多资料,对一些死机的情况做了一些分析,特此以本篇文章作总结。
微软官网统有一篇文章讲如何防止Windows程序死机的,分为三个视点(Perspective)说明了死机的概念,以及如何防止死机。参考下面的Link。
http://msdn.microsoft.com/en-us/library/windows/desktop/dd744765(v=vs.85).aspx
该文章主要谈了一些大的方向,很有道理,但没有涉及到一些具体问题。
对于死机,对应有两个英文单词就是Crash/Hang。
Crash,程序状态不明,很多时候是内存错误导致的,尤其C/C++程序中比较多见。
Hang,程序状态应该是明确的,但是该状态下,没有能力处理外部输入。主要是程序设计时没有考虑该状态的转换导致的。
导致Crash,一般性来说可能是未处理异常,例如内存错误,除零等异常。而导致Hang的,一般来说都是死锁引起的。
一,未处理异常
C#以及Java因为程序为托管代码,内存问题相对比较少(不能够说绝对没有),这里只谈未处理异常的情况。
从.Net框架2.0版本开始,Runtime环境针对为处理异常是有监控的,如果一个程序有未处理异常发生,那么会导致程序终止。
从.Net框架4.0版本开始,程序有未出处理异常,系统可以向用户弹出提示框,提示关闭程序。
所以如果程序状态异常,并且迅速终止,那么可以判断是未处理异常。
C#中提供了“未处理异常”的处理Handle句柄(UI线程异常,以及工作线程异常两种Handle),程序代码中可以定义该Handle对应的处理函数,处理程序中发生未预料的异常。C#产品级代码一定会提供比较完善的异常处理策略,所以一定会实现该Handle处理。
没有考虑到的同学,参考下面Link:
http://msdn.microsoft.com/zh-cn/library/system.windows.application.unhandledexception(v=vs.95).aspx
除此之外,还有哪些未处理异常会导致程序Crash呢?
1,Native代码的异常;
C#开发程序,一般性都会有部分呢Native代码编写的模块做底层处理,C#实现UI以及简单的网络传输等部分。
Native模块处理不当情况下,可能会有意料之外的异常发生,此时会发生Crash,可能的现象就是程序一闪就挂掉,程序上一些错误Log可能都来不及输出。
这里就需要排查Native模块,来锁定问题。如果在发生问题时,能够获得Dump文件,可以通过Dump调用栈来分析Manage代码以及Unmanaged代码的调用栈,可以定位问题所在。
2,没有恰当的处理好未处理异常;
首先,普及一个关于异常处理的常识:
一般来说,C/C++程序,如果在异常处理路径上发生新的异常的话,此时程序会进入Crash状态。
所以异常处理程序必须是Exception Free的。理论上将全部代码都是ExceptionFree才是最OK。
但是C#程序,在异常处理路径上发生新异常的话,该异常仍然会被最后的“未处理异常”的处理Handle检测并处理,中途不会Crash。
但是在该Hanle处理时,发生未处理异常的话,那肯定会进入Crash状态。
所以,如果定义了未处理异常的Handle,但是程序仍然没反馈,而直接Terminate掉,那么需要排查其未处理异常的处理函数是否被恰当的实现了,
中途是否有发生新的异常的可能性。
二,死锁
普通工作线程的死锁,可以通过Debug环境再现,或者问题发生时生成Dump文件,分析调用栈,能够分析处理。
那么除了工作线程的死锁,还有另外一种概念就是UI线程的死锁。
UI线程的职责就是处理Windows窗体消息,以及用户输入事件。
这里定义UI线程的死锁,为丧失了DumpMessage的能力。
就是UI线程已经停止处理Windows消息了,或者没有能力处理Windows消息,导致与之绑定的窗体均不能够响应用户的动作,我们说hang住了。
这个可能不是由于“锁”的问题产生的,但有类似死锁一样的现象。
1,多UI处理线程之间消息处理不当。
某些架构设计,会在在主程序有一个UI线程之外,另外设计UI线程处理部分窗口内容的处理,例如刷新视频等动作。
那么代码中就会涉及到由一个UI线程的消息,通知另外一个UI线程,去实现某些动作。UI线程之间的调用,推荐是异步的(BeginInvoke而不是Invoke)。
如果在父子窗口之间再认为设计一些同步调用,可能会导致两个UI线程之间的死锁。特别是在处理例如窗体状态Size变化,焦点迁移处理等。
2,在工作线程上创建了UI控件,导致UI界面不能够反映。
这个属于神秘事件。因为一般来说,不会有意在工作线程中创建UI控件。那为什么在工作线程上创建UI控件的事情会发生呢?
这个与.Net框架,会推迟Form/Control的窗口Handle创建有关。例如:
Form f = new Form();
该时点,创建了Form实例,但是未必创建Handle句柄。创建Handle句柄的开销要比创建一个Form实例大很多。所以.Net框架下,使用了Lazy模式,在使用到该窗体的地方再创建其窗体句柄,例如在Show的时候。那么在窗体显示之前,有针对该窗体的Callback调用的话,例如:
void CallbackHandler(Object senner , EventArg e)
{
if(this.InvokeRequired)
{
InvokeMethod delegate = new .....
} else
{
//Handle句柄没有创建的话,InvokeRequired为False,实际该函数可能运行在工作线程上。
//Do something here
//创建控件,或者其他窗口。那么控件或者窗口的上下文为该工作线程,而该工作线程无法处理消息。
}
}
此时,如果广播系统事件到该应用程序,而该程序有些控件或者窗体是在工作线程上创建的,那么回直接导致程序Hang住。因为工作线程没有处理UI事件的能力,导致整个系统都在等待该消息的处理完成。