Thread.Join никогда не возвращается в событии FormClosing при использовании метода Invoke в рабочем потоке

Я пишу приложение, используя многопоточность. Приложение в основном имеет пользовательский интерфейс и поток, выполняющий некоторую работу в фоновом режиме и обновляющий пользовательский интерфейс. Когда я закрываю форму, в событии закрытия формы я уведомляю рабочий поток об остановке. Однако по некоторым причинам он блокируется, и я понятия не имею, что вызвало его блокировку. Ниже приведен упрощенный код моей проблемы, мой реальный код сложнее.

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);
}

чтобы остановить рабочий поток в событии кнопки, рабочий поток остановится, но не в событии закрытия формы. Любая помощь будет оценена по достоинству. Спасибо.


person stoney78us    schedule 22.05.2013    source источник
comment
Почему ваша фоновая задача выполняет кучу работы, не связанной с пользовательским интерфейсом, в потоке пользовательского интерфейса?   -  person Servy    schedule 22.05.2013
comment
я пытался упростить код. Моя фактическая программа делает.   -  person stoney78us    schedule 22.05.2013
comment
@ stoney78us stoney78us Если он выполняет работу, которая занимает очень много времени, вероятно, вы смешиваете свою бизнес-логику и свой пользовательский интерфейс. Вы должны извлекать все, что не связано строго с пользовательским интерфейсом, в работу, выполняемую в фоновом режиме, а затем просто отображать эти результаты в пользовательском интерфейсе. Эта манипуляция с пользовательским интерфейсом должна быть довольно быстрой.   -  person Servy    schedule 22.05.2013


Ответы (1)


Ваше событие FormClosing ожидает закрытия фонового потока, прежде чем он позволит завершить метод. Обратите внимание, что он будет работать в потоке пользовательского интерфейса.

Ваш фоновый поток в цикле вызывает метод в потоке пользовательского интерфейса, и, поскольку вы используете Invoke, а не BeginInvoke, он ожидает завершения этого метода, прежде чем продолжить.

Пользовательский интерфейс запускает событие закрытия, ничего не делая. Поскольку он ничего не делает, он не может обрабатывать никакие другие события в цикле обработки сообщений, включая один метод, которого ожидает фоновый поток.

Оба потока ожидают друг друга, и продуктивная работа не выполняется. Это определение тупика. Так будет сидеть вечно.

Обратите внимание, что это состояние гонки; если вам повезет, и форма будет закрыта после завершения данного вызова Invoke и до следующей проверки флага (что сложно, между этими операциями проходит очень мало времени), тогда ваша программа не заблокируется.

Что касается того, как это исправить; это трудно сказать. Весь пример несколько надуманный.

  • Возможно, вам вообще не нужно вызывать пользовательский интерфейс из фонового работника; если вы на самом деле не работаете с пользовательским интерфейсом, вам, вероятно, не следует этого делать.

  • Вам действительно нужно ждать, пока фоновый рабочий закончит работу в обработчике закрытия? Возможно, что вы делаете, но часто вы бы этого не сделали.

person Servy    schedule 22.05.2013
comment
1. В моем коде UpdateUI() я фактически обновляю пользовательский интерфейс. Я не хотел публиковать всю кучу кода, поэтому я написал что-то описательное. 2. Да, я хочу, чтобы приложение безопасно завершило работу. 3. перед ожиданием остановки рабочего потока. закрытие формы устанавливает _stopFlag = true, чтобы разрешить остановку рабочего потока. Я не знаю, как это может создать мертвую блокировку. - person stoney78us; 22.05.2013
comment
Что я могу понять из вашего комментария, так это то, что метод Invoke не может завершиться, потому что он ожидает завершения события FormClosing. Однако событие FormClosing застревает в ожидании фонового потока. - person stoney78us; 22.05.2013
comment
@ stoney78us Это правильно; они оба ждут, пока другой закончит, и они оба будут ждать вечно. - person Servy; 22.05.2013
comment
мое быстрое решение использует BeginInvoke. Однако это не идеальное решение, поскольку пользовательский интерфейс закроется независимо от того, что делает UpdateUI(). - person stoney78us; 22.05.2013
comment
@stoney78us Почему UpdateUI нужно продолжать работать, если форма просто будет закрыта? На самом деле у пользователя не будет возможности увидеть, что меняется. - person Servy; 22.05.2013
comment
+1. да, еще один тупик, созданный объединением, в приложении с графическим интерфейсом. В течение 25 лет кажется, что я постоянно публикую «НЕ ЖДИТЕ В ОБРАБОТЧИКАХ СОБЫТИЙ GUI». 'НЕ ИСПОЛЬЗУЙТЕ ЖЕСТКИЕ БЛОКИРОВКИ, КАК JOIN(). В некоторых учебниках по-прежнему предполагается, что с помощью функции join() всегда следует ожидать завершения потока. Кто-нибудь, пожалуйста, убейте этих авторов, прежде чем я сойду с ума? - person Martin James; 23.05.2013