线程 - 如何使用UI交互终止工作/后台线程

ETF*_*fax 2 c# multithreading compact-framework thread-safety

Compact Framework,Windows Mobile 6,C#.

我正在对紧凑框架进行一些背景线程,并且有一个问题:终止工作线程.

代码

我有以下ThreadWorker类(来自这里的代码),在执行时,将在某些点执行检查以查看它是否应该退出.....

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

...以下表格启动线程......

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题

当我点击停止按钮时,它会调用...

myThreadWorker.Stop();
t.Join();
listResults.Items.Insert(0, "My Thread Finished.");
Run Code Online (Sandbox Code Playgroud)

...我期待的是ThreadWorker继续使用其当前的DoLongProcess()直到它完成,并仍然通过OnProgress事件处理程序和myThreadWorker_OnProgress将事件提升到UI.

然而,实际发生的是当OnProgress被引发时,应用程序冻结在线读...

 Invoke(new InsertToListBoxDelegate(InsertToListBox), new object[] { text });
Run Code Online (Sandbox Code Playgroud)

这个问题

我怎么称呼......

myThreadWorker.Stop();
t.Join();
Run Code Online (Sandbox Code Playgroud)

...并且仍然会响应来自后台线程的事件,直到它被终止?

Bri*_*eon 7

通过调用Thread.Join你已经阻止了UI线程.通过调用Control.Invoke你已经阻止了工作线程.Invoke将消息发布到UI线程的消息队列并等待它被处理.但是,由于UI线程被阻塞等待工作线程完成,因此无法开始执行转向强制工作线程停止的委托.线程现在陷入僵局.

最大的问题是Join电话.最好避免Join从UI线程调用.而是在单击后禁用停止按钮,以向用户提供已接受停止请求并处于待处理状态的反馈.您甚至可能希望在状态栏上显示一条简单的消息,说明清楚.然后,当最后一个OnProgress事件被引发时,这将是你的线程终止的信号,你可以重置表单上的所有内容.

但是,您可能想要考虑思想的根本转变.我认为这种Control.Invoke方法被过度使用了.而不是使用Control.Invoke将事件处理程序的执行编组回UI线程,您可以使用计时器对进度信息进行UI线程轮询.当有新的进度信息时,工作人员会将其发布到某个变量或数据结构.这有几个优点.

  • 它打破了Control.Invoke强加的UI和工作线程之间的紧密耦合.
  • 它将更新UI线程的责任放在它应该属于的UI线程上.
  • UI线程可以决定更新的发生时间和频率.
  • UI消息泵没有溢出的风险,就像工作线程启动的编组技术一样.
  • 在继续执行下一步之前,工作线程不必等待确认已执行更新(即,您在UI和工作线程上获得更多吞吐量).