勇哥注: 一个winform窗口,被Show()后是非模态窗口,被ShowDialog()是模态窗口,这个知识是无人不晓的。 但是,有些时候,会产生弹出窗口卡死、阻塞显示、无法置顶的诸多问题。 这一篇和勇哥一起研究一下这些现象。
先来看下现象:
下面的form是由Show()方法弹出来的。屏幕产生了阻塞显示的现象。
看下任务管理器,提示这个弹出winform是“无响应”的。
如果你此时去操作调用者的ui上的功能,主程序ui也会跟着阻塞掉,全部失去响应。
在网上搜索类似问题,找到一个提问和勇哥遇到问题的一致,但是没有答案。
只能自己研究了。
勇哥试着在show()之后执行Refresh()刷新ui。
AlarmTipsWin.StartPosition = FormStartPosition.CenterScreen; AlarmTipsWin.AddOrUpdate(alarmMsg, alarmtype); AlarmTipsWin.Show(); AlarmTipsWin.Refresh();
可以看到界面显示阻塞的现象解决了,内容已经显示完整了。
但是实际上这时候此窗口还是阻塞卡死的状态,你的鼠标指针依然在转圈,任务管理器中还是显示“无响应”。
这种现象表明,因为Show()方法是非阻塞的方法,执行完后立刻返回继续下一条代码了,
再加上一句Refresh() 也仅仅是显示内容完整了,
但是接下来阻塞问题依旧。
所以仅仅是加Refresh() 是解决不了失去响应的问题的。
这时候,我想到,如果把Show()换成 ShowDialog() 不是就解决这个问题了吗?
因为ShowDialog()是阻塞的,除非窗口关闭,否则不会执行下一条代码。
等窗口真的关闭了,因为窗口都不存在了,自然后续不存在ui阻塞的问题了。
实验证明,我这样想是对的,这样改了后,解决了问题。
问题是解决了,但其实本文想继续深究一下这背后的知识点。
我们程序猿还是需要有点工匠精神的,多探索多有益。
勇哥引入几个问题:
(1)为啥这个时候ShowDialog()的窗口没有模态窗口的效果,即不会阻止你去操作主程序的ui。
(2)为啥从主程序的ui上同样用ShowDialog()弹出这个窗口,确实有模态窗口的效果。
上面的问题,说明了,调用者的线程不同,才是上面问题的关键。
对于(1)的问题,也就是上面做实验的时候,调用者是非ui线程,即非ui线程弹出的窗口。
而对于(2)的问题,在实验的时候,是从ui线程弹出的窗口。
即问题的本质在于,你这个winform.ShowDialog()是在哪里调用的?
如果是在非ui线程中调用的,则必须使用委托。
因此你得判断调用下面的代码的时候,决定是否需要使用委托。
Form2 form2 = new Form2(); form2.ShowDialog(this);
对 Windows 窗体控件进行线程安全调用,可以查询控件的 InvokeRequired 属性。
如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke。
如果 InvokeRequired 返回 false,则直接调用控件。
下面的Form2窗体,你需要增加xShowDialog成员函数,如下:
public delegate DialogResult InvokeDelegate(Form parent); public DialogResult xShowDialog(Form parent) { if (parent.InvokeRequired) { InvokeDelegate xShow = new InvokeDelegate(xShowDialog); parent.Invoke(xShow, new object[] { parent }); return DialogResult; } return this.ShowDialog(parent); }
然后调用变成了下面:
Form2 form2 = new Form2(); form2.xShowDialog(this);
勇哥最后谈一下啥是ui线程:
C#中规定只能在ui线程上操作ui上的控件,否则会报跨线程操作ui的异常。
在winform中的定时器控件,它就是工作在ui线程上的一种定时器,因此你在里面写ui操作的代码不会报错。
但是你在winform的代码中申明一个Thread线程,此线程则不是工作在ui线程,在此线程中操作ui则会报异常。
ui线程和其它线程的区别之一是:ui线程有自己的消息循环,其它线程则没有。
通过这个区别可以判断当前线程是否是ui线程:
if (!Application.MessageLoop) { var id = Thread.CurrentThread.ManagedThreadId; throw new ThreadStateException("not on the UI thread"); }
在下面的例子里的函数是winform的Load事件代码里注册的一个类的事件处理函数。
可以看到,调用此函数的线程就不是一个ui线程。
这种情况很常见,大家一定要注意!
---------------------
作者:hackpig
来源:www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!

