2011-06-15 75 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