2011-06-15 72 views
2

Compact Framework,Windows Mobile 6,C#。我有一個在緊湊框架上的一些後臺線程,並有一個問題:終止工作線程。線程 - 如何通過UI交互來終止工作/後臺線程

守則

我有以下ThreadWorker類(從here碼),在執行時,將執行在某些點進行檢查,看它是否應該退出與否.....

public class ThreadWorker 
{ 

    public event EventHandler<ProgressEventArgs> OnProgress; 

    protected virtual void Progress(ProgressEventArgs args) 
    { 
     if (OnProgress != null) 
      OnProgress(this, args); 
    } 

    private void DoLongProcess() 
    { 
     // This will take a long time. 
     Thread.Sleep(15000); 
     Progress(new ProgressEventArgs("Some info for the UI to display.")); 
     Thread.Sleep(15000); 
    } 

    public void DoSomeBackgroundWork() 
    { 
     try 
     { 
      while (!Stopping) 
      { 
       DoLongProcess(); 
       if (Stopping) return; 

       DoLongProcess(); 
       if (Stopping) return; 

       DoLongProcess(); 
       if (Stopping) return; 

       DoLongProcess(); 
       if (Stopping) return; 
      } 
     } 
     finally 
     { 
      SetStopped(); 
     } 

     Console.WriteLine("DoSomeBackgroundWork: Terminating gracefully."); 
    } 

    /// <summary> 
    /// Lock covering stopping and stopped 
    /// </summary> 
    readonly object locker = new object(); 

    /// <summary> 
    /// Whether or not the worker thread has been asked to stop 
    /// </summary> 
    bool stopping = false; 

    /// <summary> 
    /// Whether or not the worker thread has stopped 
    /// </summary> 
    bool stopped = false; 

    /// <summary> 
    /// Returns whether the worker thread has been asked to stop. 
    /// This continues to return true even after the thread has stopped. 
    /// </summary> 
    public bool Stopping 
    { 
     get 
     { 
      lock (locker) 
      { 
       return stopping; 
      } 
     } 
    } 

    /// <summary> 
    /// Returns whether the worker thread has stopped. 
    /// </summary> 
    public bool Stopped 
    { 
     get 
     { 
      lock (locker) 
      { 
       return stopped; 
      } 
     } 
    } 

    /// <summary> 
    /// Tells the worker thread to stop, typically after completing its 
    /// current work item. (The thread is *not* guaranteed to have stopped 
    /// by the time this method returns.) 
    /// </summary> 
    public void Stop() 
    { 
     lock (locker) 
     { 
      stopping = true; 
     } 
    } 

    /// <summary> 
    /// Called by the worker thread to indicate when it has stopped. 
    /// </summary> 
    void SetStopped() 
    { 
     lock (locker) 
     { 
      stopped = true; 
     } 
    } 
} 

...和下​​面的形式啓動線程...

public partial class Test : Form 
{ 
    public Test() 
    { 
     InitializeComponent(); 
    } 

    private ThreadWorker myThreadWorker; 
    private Thread t = null; 

    private void Test_Load(object sender, EventArgs e) 
    { 
     myThreadWorker = new ThreadWorker(); 

     myThreadWorker.OnProgress += new EventHandler<ProgressEventArgs>(myThreadWorker_OnProgress); 
    } 

    private void miStart_Click(object sender, EventArgs e) 
    { 
     try 
     { 
      listResults.Items.Insert(0, "Set-up Thread."); 
      t = new Thread(myThreadWorker.DoSomeBackgroundWork); 
      t.Name = "My Thread"; 
      t.Priority = ThreadPriority.BelowNormal; 
      t.Start(); 

      listResults.Items.Insert(0, "Thread started."); 

     } 
     catch (Exception ex) 
     { 
      MessageBox.Show(ex.Message); 
     } 
    } 

    private void miStop_Click(object sender, EventArgs e) 
    { 
     try 
     { 
      listResults.Items.Insert(0, "Waiting for My Thread to terminate."); 
      listResults.Refresh(); 
      myThreadWorker.Stop(); 
      t.Join(); 
      listResults.Items.Insert(0, "My Thread Finished."); 
     } 
     catch (Exception ex) 
     { 
      MessageBox.Show(ex.Message); 
     } 
    } 

    private delegate void InsertToListBoxDelegate(String text); 
    private void InsertToListBox(String text) 
    { 
     if (InvokeRequired) 
      Invoke(new InsertToListBoxDelegate(InsertToListBox), new object[] { text }); 
     else 
     { 
      listResults.Items.Insert(0, "{0}".With(text)); 
      listResults.Refresh(); 
     } 
    } 

    void myThreadWorker_OnProgress(object sender, ProgressEventArgs e) 
    { 
     InsertToListBox(e.Text); 
    } 
} 

問題

當我點擊停止按鈕調用...

myThreadWorker.Stop(); 
t.Join(); 
listResults.Items.Insert(0, "My Thread Finished."); 

...我所期待的是爲ThreadWorker與當前DoLongProcess進行(),直到它有完成後,仍然通過OnProgress事件處理程序和myThreadWorker_OnProgress將事件提交給UI。

然而,實際發生的事情是,當OnProgress提高,應用凍結上線閱讀...

Invoke(new InsertToListBoxDelegate(InsertToListBox), new object[] { text }); 

問題

我怎麼叫...

myThreadWorker.Stop(); 
t.Join(); 

...並仍然響應後臺線程的事件,直到它終止?

回答

7

通過調用已阻塞UI線程Thread.Join。通過調用Control.Invoke您已阻止工作線程。 Invoke向UI線程的消息隊列發送消息並等待它被處理。但是,由於UI線程被阻塞等待工作線程完成,它不能開始執行委託,這將強制工作線程停止。線程現在處於死鎖狀態。

最大的問題是撥打Join。最好避免從UI線程調用Join。相反,在點擊後停用停止按鈕,以向用戶提供停止請求已被接受並且正在等待的反饋。你甚至可能希望在狀態欄上顯示一條簡單的消息,說明清楚。然後,當最後一個OnProgress事件發生時,這將是您的信號,即線程已終止,您可以重置表單上的所有內容。

但是,您可能要考慮思想的根本轉變。我認爲Control.Invoke方法是過度使用。而不是使用Control.Invoke將事件處理程序的執行返回到UI線程,您可以讓UI線程使用計時器輪詢進度信息。當新的進度信息可用時,工作人員將把它發​​布到一些變量或數據結構。這有幾個優點。

  • 它打破了用戶界面和工作者線程之間的緊密耦合,這些線程與Control.Invoke強加。
  • 它將UI線程更新的責任置於UI線程上,無論如何它應該屬於它。
  • UI線程可以決定更新應該發生的時間和頻率。
  • 不存在UI消息泵被超載的風險,這與工作線程啓動的封送處理技術的情況相同。
  • 工作線程不必等待確認該更新與它的下一個步驟之前執行(即你同時在用戶界面和工作線程的詳細吞吐量)。
+0

非常感謝Brian,第1段真的幫助澄清了一些混淆。還要感謝替代解決方案的想法 - 我會給出一些嘗試.....我不知道現在還有什麼/如何(我期待更多的問題!!!!)再次感謝。 ETFairfax – ETFairfax 2011-06-15 14:21:21

3

只需用BeginInvoke替換Invoke即可。如果這是不可能的並且必須是同步的,請重現本文中的DoEvent技巧:http://www.codeproject.com/KB/cs/workerthread.aspx

更換t.Join()與此循環:

 
for(;;) 
{ 
    if (t.Join(100)) // set appropriate timeout 
    { 
     break; 
    } 

    Application.DoEvents(); // resolve deadlock 
} 
+0

請不要使用DoEvents http://blog.codinghorror.com/is-doevents-evil/ – 2015-08-19 11:43:10

0

不要在UI線程上使用Join!這是一個完美的例子,人們如何打破任何編程模型...取消後臺工作者的乾淨方式是安裝一個CancellationTokenSource,並傳遞給後臺工作人員一個CancellationToken的實例。如果後臺操作應該取消,您可以在安裝的實例上調用CancellationTokenSource.Cancel。然後,您可以隨意檢查令牌的值,只是暫時離開當前的執行路徑。我還建議使用更高級別的異步API(任務,APM),而不是手動產生線程。

+0

你能詳細說明爲什麼它會「破壞任何編程模型」?我見過的所有例子都使用join!另外,我不認爲在緊湊框架中可以使用CancellationToken。 – ETFairfax 2011-06-16 12:24:36