本文主要讲讲C#窗体的程序中一个经常遇到的情况,就是在退出窗体的时候的,发生了退出的异常。
我们先来看看一个典型的场景,定时从PLC或是远程服务器获取数据,然后更新界面的标签,基本上实时更新的。我们可以把模型简化,简化到一个form窗体里面,开线程定时读取
1 | public partial class Form1 : Form |
我们很有可能这么写,当我们点击了窗口的时候,会出现如下的异常

发送这个问题的根本原因在于,当你点击了窗体关闭,窗体所有的组件开始释放资源,但是线程还没有立即关闭,所以针对上诉的代码,我们来进行优化。
1. 优化不停的创建委托实例
1 | private void ThreadCapture( ) |
这样就避免了每隔200ms频繁的创建委托实例,这些委托实例在GC回收数据时又要占用内存消耗,随便用户感觉不出来,但是良好的开发习惯就用更少的内存,执行很多的东西。
我刚开始思考如果避免退出异常的时候,既然它报错为空,我就加个判断呗
1 | private void ThreadCapture( ) |
显示界面的时候,判断下句柄是否创建,当前是否被释放。出现异常的频率少了,但是还是会出。下面提供了一个简单粗暴的思路,既然报错已释放,我就捕获异常
1 | private void ThreadCapture( ) |
这样子就简单粗暴的解决了,但是还是觉得不太好,所以不采用try…catch,换个思路,我自己新增一个标记,指示窗体是否显示出来。当窗体关闭的时候,复位这个标记
1 | private void ThreadCapture( ) |
整个程序变成了这个样子,我们再多次测试测试,还是经常会出,这时候我们需要考虑更深层次的事了,我程序退出的时候需要做什么事?把采集线程关掉,停止刷新,这时候才能真正的退出
这时候就需要同步技术了,我们继续改造
1 | private void ThreadCapture( ) |
根据思路我们写出了这个代码。运行了一下,结果卡住不动了,分析下原因是刚点击退出的时候,如果发现了Invoke显示的时候,就会形成死锁,所以方式一,改下下面的机制
1 | private void ThreadCapture( ) |
Invoke的时候改成异步的机制,就可以解决这个问题,但是BeginInvoke方法并不是特别的安全的方式,而且我们在退出的时候有可能会卡那么一会会,我们需要想想有没有更好的机制。
如果我们做一个等待的退出的窗口会不会更好?既不卡掉主窗口,又可以完美的退出,我们新建一个小窗口

去掉了边框,界面就是这么简单,然后改造代码
1 | public partial class FormQuit : Form |
我们只要把这个退出前的操作传递给退出窗口即可以
1 | private void ThreadCapture( ) |
至此你的程序再也不会出现上面的对象已经被释放的异常了,退出的窗体显示时间不定时,0-200毫秒。为了有个明显的逗留作用,我们多睡眠200ms,这样我们就完成了最终的程序,如下:
1 | public partial class Form1 : Form |