c# - 线程 - 如何通过 UI 交互终止工作/后台线程

标签 c# multithreading compact-framework thread-safety

紧凑型框架,Windows Mobile 6,C#。

我正在研究紧凑型框架上的一些后台线程,并有一个问题:终止工作线程。

代码

我有以下 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();

...并且在后台线程终止之前仍然响应来自后台线程的事件?

最佳答案

通过调用 Thread.Join,您已经阻塞了 UI 线程。通过调用 Control.Invoke,您已经阻塞了工作线程。 Invoke 将消息发布到 UI 线程的消息队列并等待它被处理。但是,由于 UI 线程在等待工作线程完成时被阻塞,因此它无法开始执行委托(delegate),这又迫使工作线程停止。线程现在处于死锁状态。

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

但是,您可能需要考虑彻底改变想法。我认为 Control.Invoke 方法被滥用了。您可以让 UI 线程使用计时器轮询进度信息,而不是使用 Control.Invoke 将事件处理程序的执行编码回 UI 线程。当有新的进度信息可用时,worker 会将其发布到某个变量或数据结构中。这有几个优点。

  • 它打破了 Control.Invoke 强加的 UI 和工作线程之间的紧密耦合。
  • 它将更新 UI 线程的责任放在它应该属于的 UI 线程上。
  • UI 线程可以决定更新的时间和频率。
  • 不存在 UI 消息泵溢出的风险,而工作线程启动的编码(marshal)处理技术会出现这种情况。
  • 工作线程不必等待执行更新的确认就可以继续执行后续步骤(即,您在 UI 和工作线程上获得更多吞吐量)。

关于c# - 线程 - 如何通过 UI 交互终止工作/后台线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6357176/

相关文章:

c# - 方向取决于数据时的 OrderByWithDirection

multithreading - 无锁数据结构中需要多少个ABA标签位?

c# - 从 C#(紧凑型框架)访问 VC++ Dll

c# - Dns.GetHostEntry 在 Windows Mobile 上返回错误的 IP 地址

c# - 重命名正在运行的应用程序 - 危险吗?

c# - 如何将 ISortedEnumerable<XElement> 添加到 XElement?

c# - 为什么我可以使用 lambda 表达式代替回调委托(delegate)?

java - JMM 中的因果关系要求

ios - 执行许多阻塞主线程的 UIKit 操作

c# - 获取附加到 Windows CE 设备的 USB 驱动器名称