簡體   English   中英

在關閉主窗體之前關閉側面線程

[英]Closing side thread before closing the main form

我正在使用C#在Visual Studio .NET 2010中使用Windows窗體創建GUI應用程序。 我有多個線程,例如:

  1. 正在ping目標設備的線程,以確保連接不會丟失
  2. 從設備請求和接收測量結果的線程
  3. 一個不斷用這些結果更新主窗體的線程。 此線程使用Control.Invoke()調用主窗體控件中的更改。 我的目標:當用戶在主窗體上按(x)時,我想確保正確關閉窗體。 這意味着我希望我的連接關閉,所有側線程終止等。我正在嘗試使用FormClosing事件。 在那里我將KillThreads標志設置為true並等待側線程終止。 每個都有一張支票,比如
if(KillThreads)
    return;

但是有一個問題。 如果我試圖等待所有線程終止

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

線程N3凍結被Invoke()調用阻塞; 當然是,因為主gui線程處於無限循環中。 所以表格結束會永遠延遲。

如果我將on-close操作實現為線程過程4,並在FromClosing事件處理程序中創建一個新線程,則表單會在線程4終止之前立即關閉,而Invoke()會拋出錯誤-t。 4無法及時關閉線程3。 如果我添加Join(), Invoke()則再次阻止Join(), Invoke()調用,並且線程3永遠不會終止,因此線程4將永遠等待。

我該怎么辦? 有什么方法可以解決這個問題嗎?

首先決定你是否真的需要使用Invoke 我現在已經有一段時間了,我認為Invoke是從工作線程啟動后在UI線程上發生操作最常用的技術之一。 另一種方法是在工作線程中創建某種類型的消息對象,描述需要在UI線程上執行的操作並將其放入共享隊列。 然后,UI線程將使用System.Windows.Forms.Timer在某個時間間隔輪詢此隊列,並導致每個操作發生。 這有幾個優點。

  • 它打破了Invoke強加的UI和工作線程之間的緊密耦合。
  • 它將更新UI線程的責任放在它應該屬於的UI線程上。
  • UI線程可以決定更新的發生時間和頻率。
  • UI消息泵沒有溢出的風險,就像工作線程啟動的編組技術一樣。
  • 在繼續執行下一步之前,工作線程不必等待確認已執行更新(即,您在UI和工作線程上獲得更多吞吐量)。
  • 處理關閉操作容易得多,因為幾乎可以消除在請求工作線程正常終止時通常會出現的所有競爭條件。

當然, Invoke非常有用,並且有很多理由繼續使用這種方法。 如果您決定繼續使用Invoke請閱讀更多內容。

一個想法是在Form.Closing事件中設置KillThreads標志,然后通過設置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抽取消息。

我會在你的線程中有一個執行Invoke的頂級異常處理程序,如果它發現KillThreads為真,它會吞下Invoke產生的異常。 然后你需要的是主線程將KillThreads設置為true並允許消息循環結束。

我的一般技術,而不是一個簡單的布爾變量,將使用ManualResetEvent作為其他線程中止的信號。 這樣做的主要原因是,如果我有長時間運行的非UI線程,有時候想睡覺。 通過使用該事件,我可以通過調用Event.WaitOne替換對Thread.Sleep的調用,這意味着如果線程必須中止,線程將始終立即喚醒。


在任何一種情況下,我通常不會等待線程發出完成信號。 我相信他們會及時這樣做,並且不會傾向於使用依賴於未運行的線程的后續代碼。 如果你確實有這種情況,也許你可以詳細說明它是什么?

您可以在所有線程上調用Thread.Join並通過Thread.Abort如果它們無法在給定的超時內加入。 Abort的調用在線程的上下文中引發了ThreadAbortException,結束了這個線程。

我認為主要觀點(主線程是否等待其他線程結束,或者不是)在調用GUI線程上的操作之前檢查KillThreads是否為false,除非你真的想在之前向用戶顯示一些內容退出,在這種情況下你應該循環你的主線程,檢查所有工作線程的ThreadState或IsAlive屬性,以確定它們是否已經完成然后退出(或者在完成所有工作線程后執行任何操作) )。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM