如果在工作线程中使用Invoke方法,Thread.Join永远不会在FormClosing事件中返回

时间:2013-05-22 18:41:14

标签: c# multithreading user-interface thread-safety formclosing

我正在使用多线程编写应用程序。该应用程序基本上有一个UI和一个线程在后台做一些工作并更新UI。当我关闭表单时,在formclosing事件中,我通知工作线程停止。然而,由于某些原因,它阻止了,我不知道是什么导致它阻止。下面是我的问题的简化代码,我的实际代码更复杂。

namespace CmdTest
{
    public partial class Form1 : Form
    {
        Thread _workerThread;
        static object _lock;
        static bool _stopFlag;

        public Form1()
        {
            _lock = new object();
            _stopFlag = false;
            _workerThread = new Thread(new ThreadStart(ThreadDoWork));

            InitializeComponent();

            _workerThread.Start();
        }

        delegate void UpdateUI();
        public void UpdateUICallback()
        {
            //Doing stupid things
            int i = 0;
            while (i < 10000)
            {
                i++;
            }
        }

        public void ThreadDoWork()
        {

            if (this.InvokeRequired)
            {
                UpdateUI updateUI = new UpdateUI(UpdateUICallback);
                while (true)
                {
                    //telling the UI thread to update UI.
                    this.Invoke(updateUI);

                    lock (_lock)
                    {
                        if (_stopFlag)
                            return;
                    }
                }
            }
        }       

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //tell the worker thread to terminate.
            lock (_lock)
            {
                _stopFlag = true;
                Monitor.Pulse(_lock);
            }

            while (!_workerThread.Join(0))
            { 
            }
        }
    }
}

问题是如果我使用

lock (_lock)
{
   _stopFlag = true;
   Monitor.Pulse(_lock);
}

要在按钮事件中停止工作线程,工作线程将停止但不在窗体关闭事件中。任何帮助,将不胜感激。感谢。

1 个答案:

答案 0 :(得分:3)

您的FormClosing事件在方法结束之前等待后台线程关闭。请注意,它将在UI线程中运行。

你的后台线程在循环中调用UI线程中的方法,因为你使用Invoke而不是BeginInvoke,它正在等待该方法完成后再继续。

UI正在运行结束事件,坐在那里什么都不做。由于它什么都不做,它无法处理消息循环中的任何其他事件,包括后台线程正在等待的一种方法。

两个线程都在彼此等待,并且没有进行任何有效的工作。这是死锁的定义。它会永远这样。

请注意,这是竞争条件;如果你足够幸运,在给定的Invoke调用完成之后和下一次检查标志之前关闭表单(这很难;它在这些操作之间花费的时间很少)那么你的程序就不会死锁

至于如何解决它;这很难说。整个例子有点人为。

  • 也许您根本不需要从后台工作者调用UI;如果你实际上没有做UI工作,你可能不应该这样做。

  • 你真的需要等待后台工作人员在你的结算处理程序中完成吗?你可以这样做,但通常你不会这样做。