从另一个线程启动时,我的表单无法正确显示
情况如下:
我正在开发具有以下结构的简单应用程序:
- FormMain(启动点)
- 表格通知
- CompleFunctions
正确的?
好吧,在FormMain中,我具有以下功能:
private void DoItInNewThread(ParameterizedThreadStart pParameterizedThreadStart, object pParameters, ThreadPriority pThreadPriority) { Thread oThread = new Thread(pParameterizedThreadStart); oThread.CurrentUICulture = Settings.Instance.Language; oThread.IsBackground = true; oThread.Priority = pThreadPriority; oThread.Name = "μRemote: Background operation"; oThread.Start(pParameters); }
因此,每次需要调用位于ComplexFunctions上的耗时方法时,我都会执行以下操作:
// This is FormMain.cs string strSomeParameter = "lala"; DoItInNewThread(new ParameterizedThreadStart(ComplexFunctions.DoSomething), strSomeParameter, ThreadPriority.Normal);
另一个类是FormNotification,它是一个Form,用于向用户显示过程的某些信息。
可以从FormMain或者ComplexFunctions调用此FormNotification。
例子:
// This is ComplexFunctions.cs public void DoSomething(string pSomeParameter) { // Imagine some time consuming task FormNotification formNotif = new FormNotification(); formNotif.Notify(); }
FormNotify有一个计时器,因此,在10秒钟后关闭表单。我不使用formNotif.ShowDialog,因为我不想把重点放在此Form上。
我们可以检查此链接以查看我在"通知"中正在做什么。
好的,这是问题所在:
当我从ComplexMain中的另一个线程调用的ComplexFunction中调用FormNotify时,此FormNotify会在几毫秒后消失。
与执行以下操作时具有相同的效果:
using(FormSomething formSomething = new FormSomething) { formSomething.Show(); }
如何避免这种情况?
这些是我不想使用的可能解决方案:
- 在FormNotify中使用Thread.Sleep(10000)
- 使用FormNotif.ShowDialog()
这是一种简化的方案(FormNotify还会执行其他一些花哨的内容,仅停留10秒钟,但与看到问题无关)。
谢谢你的时间!!!
拜托,对不起我的英语。
解决方案
不允许从其他线程进行WinForms调用。查看表单中的BeginInvoke-我们可以调用一个委托以从UI线程显示表单。
编辑:从注释(不要将CheckForIllegalCrossThreadCalls设置为false)。
更多信息
几乎每个GUI库都设计为仅允许在用于此目的的单个线程(称为UI线程)中进行更改GUI的调用。如果我们在另一个线程中,则需要安排调用以更改要在UI线程中进行的GUI。在.NET中,执行此操作的方法是调用Invoke(同步)或者BeginInvoke(异步)。等效的Java Swing调用是invokeLater()-几乎每个GUI库中都有类似的函数。
使用SetWindowPos API调用来确保通知表单是最顶层的窗口。这篇文章解释了如何:
http://www.pinvoke.net/default.aspx/user32/SetWindowPos.html
有一种叫做线程亲和力的东西。 WinForm应用程序中有两个线程,一个用于渲染,一个用于管理用户界面。我们只处理用户界面线程。渲染线程在后台保持隐藏运行。在UI线程上创建的唯一对象可以操纵UI,即,这些对象与UI线程具有线程相似性。
从那时起,我们尝试从与UI线程不同的线程更新UI(显示通知)。因此,在工作线程中定义一个委托,并使FormMain侦听此事件。在事件处理程序(在FormMain中定义)中,编写代码以显示FormNotify。
当我们想要显示通知时,从工作线程中触发事件。
几乎每个GUI库都设计为仅允许在用于此目的的单个线程(称为UI线程)中进行更改GUI的调用。如果我们在另一个线程中,则需要安排调用以更改要在UI线程中进行的GUI。在.NET中,执行此操作的方法是调用Invoke(同步)或者BeginInvoke(异步)。等效的Java Swing调用是invokeLater()-几乎每个GUI库中都有类似的函数。
有一种叫做线程亲和力的东西。 WinForm应用程序中有两个线程,一个用于渲染,一个用于管理用户界面。我们只处理用户界面线程。渲染线程在后台保持隐藏运行。在UI线程上创建的唯一对象可以操纵UI,即,这些对象与UI线程具有线程相似性。
从那以后,我们尝试从与UI线程不同的线程更新UI(显示通知)。因此,在工作线程中定义一个委托,并使FormMain侦听此事件。在事件处理程序(在FormMain中定义)中,编写代码以显示FormNotify。
当我们想要显示通知时,从工作线程中触发事件。
当控件的创建线程以外的其他线程尝试访问该控件的方法或者属性之一时,通常会导致不可预测的结果。常见的无效线程活动是对访问控件的Handle属性的错误线程的调用。将CheckForIllegalCrossThreadCalls设置为true可以在调试时更轻松地查找和诊断此线程活动。请注意,在调试器外部启动应用程序时,非法的跨线程调用将始终引发异常。
注意:只能在DEBUGGIN情况下将CheckForIllegalCrossThreadCalls设置为ture。将会出现无法预料的结果,并且我们最终将试图追逐难以找到书本的错误。
假设我们在表单中有按钮,并且想在用户单击该按钮时打开另一个表单Form1
private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(new ThreadStart(this.ShowForm1)); t.Start(); }
我们需要做的就是检查InvokeRequired属性,如果是,则调用传递ShowForm1委托的表单的Invoke方法,该方法将最终递归调用,其中InvokeRequired为false
delegate void Func(); private void ShowForm1() { if (this.InvokeRequired) { Func f = new Func(ShowForm1); this.Invoke(f); } else { Form1 form1 = new Form1(); form1.Show(); } }