C#中有关资源、BeginInvoke, Invoke和事件的事情
事情是这么来的,我开发的一个程序报了一个错误 “在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke错误”。
然后我在网上查资料,发现一个有意思的问题,文章出处为“在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke”错误。
问题
程序是如下这样的。
Form1有Button1、Button2和Button3两个按钮,Button2是动态的new Form2窗体,Form2中注册了Form1中的事件.
Button1的作用是关闭所有new出来的Form2窗体,并且把其资源释放掉(Dispose()).
Button3是让Form2中注册Form1了的事件都发生。
操作:
先点Button2 new出几个Form2的窗体,然后点击Button1释放掉所有的资源,然后再单击Button2 new出几个Form2的窗体,再单击Button3问题就出现了。老是提示"在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke"。
作者给出的解析
“在Window窗体程序开发的时候,如果使用多线程编程,在子线程中访问主线程窗体内的控件,就需要使用控件的Control.Invoke方法或者BeginInvoke方法。但是有时候因为Window执行速度太快,尤其是你写代码的时候在InitializeComponent();完成之前起了一个线程去执行某些操作,涉及到窗体控件的,当你在调用Control.Invoke的时候,就可能出现 “在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke”错误。
这个解释我感觉十分有道理。
给出的解决方法
解决的办法就是让线程等待,直到窗口句柄创建完毕:
//防止在窗口句柄初始化之前就走到下面的代码
while (!this.IsHandleCreated)
{
;
}
this.BeginInvoke(new ProListIndexChangedDelegate(GetProLyric));
//根据不同情况也可以:
if (this.IsHandleCreated)
BeginInvoke(new ProListIndexChangedDelegate(GetProLyric));
其它人(钻葛格)给的解决方法:
一个更巧妙的方法,只要在BeginInvoke方法的调用语句前再加一句:IntPtr i = this.Handle;就OK了,这比死循环配合this.IsHandleCreated的判断方法更简洁,因为this.Handle这个属性本身就对应一个方法,取不到句柄,程序就不会向下进行。
如果以上方式时,IntPtr IsHandleCreated = this.Handle;报错:“从不是创建他的线程访问”。则可把this换成你想提取的句柄所在的线程中的窗体类(或其中任一控件)的实例名。
本人也就是通过该方法最终解决问题的。也很有可能是作者给出的解释的那种原因。
结果
作者的解决的结果
再次新建窗体的窗口句柄在Show()之后都通过IsHandleCreated属性检测过了,再调用Invoke()之前都创建了,还是会出错。那问题出在哪里呢?一开始还以为单击Button1没有真正的释放资源,然后我就强制GC了,然后检测不到以前的窗体了,但是还是不行,还是有问题。
然后就想到了“事件”上面来了。是不是事件的原因呀?以前创建的窗体没有注销该事件,把之前所有的窗体都注销该事件,问题就解决了......
有留言认为作者的意思是:根本不是没创建的原因,而是没有清空事件。
我觉得确实应该是访问了没有创建的UI资源造成的!作者的解决方法是对的,但是说法是错的。
也就是作者在Button1中释放(Close)了Form2了。而却没有取消Form2中所订阅的Form1的事件。
此时当点击了button3的话,将激发Form1中事件的委托链,而已经释放掉的Form2对象的事件的响应,由于找到不到该对象资源了,造成了老是提示"在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke"。
解决办法缺失如作者所说,去除已经被释放对象的订阅的事件即可。
没有整理与归纳的知识,一文不值!高度概括与梳理的知识,才是自己真正的知识与技能。 永远不要让自己的自由、好奇、充满创造力的想法被现实的框架所束缚,让创造力自由成长吧! 多花时间,关心他(她)人,正如别人所关心你的。理想的腾飞与实现,没有别人的支持与帮助,是万万不能的。