我正在使用C#
在Visual Studio .NET 2010中使用Windows窗体创建GUI应用程序。
我有多个线程,例如:
Control.Invoke()
调用主窗体控件中的更改。
我的目标:当用户在主窗体上按(x)时,我想确保正确关闭窗体。这意味着我希望我的连接关闭,所有侧线程终止等。
我正在尝试使用FormClosing
事件。
在那里我将KillThreads标志设置为true并等待侧线程终止。每个都有一张支票,如if(KillThreads)
return;
但是有一个问题。 如果我试图等待所有线程被终止
for(;;)
if(ActiveSideThreads == 0)
break;
closeConnection();
线程N3冻结被Invoke()
电话阻止;
当然是,因为主gui线程处于无限循环中。
所以表格结束会永远延迟。
如果我将前关闭操作实现为线程过程4,并在FromClosing事件处理程序中创建一个新线程,
表单在线程4终止之前立即关闭,Invoke()
抛出错误 - t。 4无法及时关闭线程3。
如果我添加Join(), Invoke()
调用再次被阻止,并且线程3永远不会被终止,那么线程4将永远等待。
我该怎么办? 有什么方法可以解决这个问题吗?
答案 0 :(得分:6)
首先决定你是否真的需要使用Invoke
。现在已经有一段时间了,我认为Invoke
是从工作线程启动后在UI线程上发生操作最常用的技术之一。另一种方法是在工作线程中创建某种类型的消息对象,描述需要在UI线程上执行的操作并将其放入共享队列。然后,UI线程将使用System.Windows.Forms.Timer
在某个时间间隔内轮询此队列,并导致每个操作发生。这有几个好处。
Invoke
强加的UI和工作线程之间的紧密耦合。当然,Invoke
非常有用,并且有很多理由继续使用这种方法。如果您决定继续使用Invoke
,请阅读更多内容。
一个想法是在KillThreads
事件中设置Form.Closing
标记,然后通过设置FormClosingEventArgs.Cancel = true
取消关闭表单。您可能希望让用户知道已请求关闭并且正在进行中。您可能希望禁用表单上的某些控件,以便无法启动新操作。所以基本上我们发信号通知已经请求了关闭,但是推迟关闭表单直到工作线程先关闭。要执行此操作,您可能需要启动一个定期检查工作线程是否已结束的计时器,如果有,则可以调用Form.Close
。
public class YourForm : Form
{
private Thread WorkerThread;
private volatile bool KillThreads = false;
private void YourForm_Closing(object sender, FormClosingEventArgs args)
{
// Do a fast check to see if the worker thread is still running.
if (!WorkerThread.Join(0))
{
args.Cancel = true; // Cancel the shutdown of the form.
KillThreads = true; // Signal worker thread that it should gracefully shutdown.
var timer = new System.Timers.Timer();
timer.AutoReset = false;
timer.SynchronizingObject = this;
timer.Interval = 1000;
timer.Elapsed =
(sender, args) =>
{
// Do a fast check to see if the worker thread is still running.
if (WorkerThread.Join(0))
{
// Reissue the form closing event.
Close();
}
else
{
// Keep restarting the timer until the worker thread ends.
timer.Start();
}
};
timer.Start();
}
}
}
上面的代码调用Join
,但它指定超时为0,这会导致Join
调用立即返回。这应该保持UI抽取消息。
答案 1 :(得分:1)
我的线程中有一个执行Invoke的顶级异常处理程序,如果发现Invoke
为真,则会吞下KillThreads
引起的异常。然后你需要的是主线程将KillThreads设置为true并允许消息循环结束。
我的一般技术,而不是简单的布尔变量,将使用ManualResetEvent
作为其他线程中止的信号。这样做的主要原因是,如果我有长时间运行的非UI线程,有时候想睡觉。通过使用该事件,我可以通过调用Thread.Sleep
替换对Event.WaitOne
的调用,这意味着如果线程必须中止,该线程将始终立即唤醒。
在任何一种情况下,我通常不会等待线程发出信号完成。我相信他们会及时这样做,并且不会倾向于使用依赖于未运行的线程的后续代码。如果你确实有这种情况,也许你可以详细说明它是什么?
答案 2 :(得分:0)
您可以在所有线程上调用Thread.Join
,如果在给定超时内无法加入,则可以通过Thread.Abort
终止。 Abort的调用在线程的上下文中引发了ThreadAbortException,结束了这个线程。
答案 3 :(得分:0)
我认为主要观点(主线程是否等待其他线程结束,或者不是)在调用GUI线程上的操作之前检查KillThreads是否为false,除非你真的想要向用户就在退出之前,在这种情况下你应该只循环你的主线程,检查所有工作线程的ThreadState或IsAlive属性,以确定它们是否已经完成然后退出(或者在所有工作者之后做任何需要的事情)线程完成了。)