在关闭主窗体之前关闭侧面线程

时间:2011-09-08 07:34:38

标签: c# multithreading visual-studio-2010 user-interface

我正在使用C#在Visual Studio .NET 2010中使用Windows窗体创建GUI应用程序。 我有多个线程,例如:

  1. 正在ping目标设备以确保连接不丢失的线程
  2. 请求并接收来自设备的测量结果的线程
  3. 使用这些结果不断更新主窗体的线程。此线程使用Control.Invoke()调用主窗体控件中的更改。 我的目标:当用户在主窗体上按(x)时,我想确保正确关闭窗体。这意味着我希望我的连接关闭,所有侧线程终止等。 我正在尝试使用FormClosing事件。 在那里我将KillThreads标志设置为true并等待侧线程终止。每个都有一张支票,如
  4. if(KillThreads)
        return;
    

    但是有一个问题。 如果我试图等待所有线程被终止

    for(;;)
    if(ActiveSideThreads == 0)
        break;
    closeConnection();
    

    线程N3冻结被Invoke()电话阻止; 当然是,因为主gui线程处于无限循环中。 所以表格结束会永远延迟。

    如果我将前关闭操作实现为线程过程4,并在FromClosing事件处理程序中创建一个新线程, 表单在线程4终止之前立即关闭,Invoke()抛出错误 - t。 4无法及时关闭线程3。 如果我添加Join(), Invoke()调用再次被阻止,并且线程3永远不会被终止,那么线程4将永远等待。

    我该怎么办? 有什么方法可以解决这个问题吗?

4 个答案:

答案 0 :(得分:6)

首先决定你是否真的需要使用Invoke。现在已经有一段时间了,我认为Invoke是从工作线程启动后在UI线程上发生操作最常用的技术之一。另一种方法是在工作线程中创建某种类型的消息对象,描述需要在UI线程上执行的操作并将其放入共享队列。然后,UI线程将使用System.Windows.Forms.Timer在某个时间间隔内轮询此队列,并导致每个操作发生。这有几个好处。

  • 它打破了Invoke强加的UI和工作线程之间的紧密耦合。
  • 它将更新UI线程的责任放在它应该属于的UI线程上。
  • UI线程决定更新的发生时间和频率。
  • UI消息泵没有溢出的风险,就像工作线程启动的编组技术一样。
  • 在继续执行下一步之前,工作线程不必等待确认已执行更新(即,您在UI和工作线程上获得更多吞吐量)。
  • lot 更容易处理关机操作,因为您几乎可以消除在请求工作线程正常终止时通常会出现的所有竞争条件。

当然,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属性,以确定它们是否已经完成然后退出(或者在所有工作者之后做任何需要的事情)线程完成了。)