C # .NET Threading Вопрос - PullRequest
       43

C # .NET Threading Вопрос

2 голосов
/ 02 апреля 2011

Я столкнулся с проблемой связи между потоками в приложении C # .NET. Надеюсь, что кто-то направит меня в правильном направлении относительно возможных решений.

У меня есть приложение на C # .NET. Это приложение для Windows. Мое приложение имеет два потока: один поток является основным (поток пользовательского интерфейса), а другой - дочерним. Давайте назовем дочерний поток «workerThread» В приложении используется только одна форма. Пусть эта форма называется «MainForm»

Дочерний поток запускается при загрузке MainForm (для запуска потока использовался обработчик событий формы «Load»)

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

У меня есть другой класс (помимо класса MainForm), который содержит метод, который я выполняю в дочернем потоке. Давайте назовем этот второй класс "WorkerClass". Я передаю ссылку на текущую форму (MainForm) в конструктор "WorkerClass"

У меня есть кнопка «stop» в главной форме, которая устанавливает «stopWork» в «true», если ее щелкнуть, а затем вызывает «workerThread.Join ()», чтобы дождаться завершения обработки дочерним потоком.

В дочернем потоке метод doWork продолжает проверять состояние «parentForm.stopWork» внутри цикла для . Если для «stopWork» установлено значение «истина», цикл прерывается, а затем метод завершается.

Теперь проблема в том, что после нажатия кнопки «Стоп» приложение зависает.

Я вставляю части кода ниже, чтобы его было легче понять:

public partial class MainForm : Form
{
    Thread workerThread = null;
    ThreadStart workerThreadStart = null;
    WorkerClass workerClass = null;

    public bool stopWork = true;

    /*.......... some code ............*/

    private void MainForm_Load(object sender, EventArgs e)
    {
        workerThreadStart = new ThreadStart(startWork);
        workerThread = new Thread(workerThreadStart);
        stopWork = false;
        workerThread.Start();
    }

    private void startWork()
    {
        workerClass = new WorkerClass(this);
    }

    private void buttonStop_Click(object sender, EventArgs e)   //"stop" button
    {
        if (workerThread != null)
        {
            if (workerThread.IsAlive == true)
            {
                stopWork = true;
                workerThread.Join();
            }
        }
    }

    /*.......... some more code ............*/

}

public class WorkerClass
{
    MainForm parentForm=null;

    /*......... some variables and code ........*/

    public WorkerClass(MainForm parentForm)
    {
        this.parentForm=parentForm;
    }

    /* .............. some more code ...........*/

    public void doWork()
    {
       /*.......... some variables and code ...........*/

       for(int i=0;i<100000;i++)
       {
           // ** Here is the check to see if parentForm has set stopWork to true **
           if(parentForm.stopWork==true)
              break;

           /*......... do some work in the loop ..........*/


       }

    }

    /********* and more code .........*/
}

Думаю, я знаю, в чем проблема. Проблема заключается в том, что метод doWork в дочернем потоке пытается получить доступ к переменной stopWork в родительской форме, когда родительская форма уже заблокирована путем вызова метода workerThread.Join (). Итак, я думаю, что это проблема "тупика".

Прав ли я в определении проблемы? Или я не прав и проблема кроется где-то еще?

Если это действительно тупик, каковы возможные решения этой проблемы?

Я немного погуглил и нашел много ресурсов о синхронизации потоков и о том, как избежать тупиков. Но я не мог понять, как применить их конкретно к моей проблеме.

Буду очень признателен за любую помощь или руководство по решению этой проблемы.

Ответы [ 2 ]

3 голосов
/ 02 апреля 2011

Да, код, который вы написали, очень уязвим к тупику. Класс BackgroundWorker особенно склонен вызывать такого рода тупики.

Проблема находится в коде, который мы не видим в вашем фрагменте, WorkerClass. Вы, безусловно, делаете там что-то, что так или иначе влияет на пользовательский интерфейс, что всегда является основной причиной, по которой стоит сначала создать поток. Вы, вероятно, используете Control.Invoke () для запуска некоторого кода в потоке пользовательского интерфейса и обновления элемента управления. Возможно также, чтобы сигнализировать, что рабочий поток завершен, и, скажем, установить для свойства Enable кнопки назад значение true.

Это тупиковый город, такой код не может работать до тех пор, пока поток пользовательского интерфейса не будет бездействовать, возвращаясь к циклу обработки сообщений. Он никогда не будет простаивать в вашем случае, он застрял в Thread.Join (). Рабочий поток не может завершиться, потому что поток пользовательского интерфейса не будет простаивать, поток пользовательского интерфейса не может простаивать, потому что рабочий поток не завершается. Тупик.

У BackgroundWorker также есть эта проблема, событие RunWorkerCompleted не может выполняться, если поток пользовательского интерфейса не занят. Что вам нужно сделать, это , а не блокировать поток пользовательского интерфейса. Проще сказать, чем сделать, BGW может помочь вам сделать это правильно, потому что он запускает событие, когда завершается. Вы можете заставить это событие делать то, что вы сейчас делаете в коде после вызова Thread.Join (). Вам понадобится логический флаг в вашем классе, чтобы указать, что вы находитесь в состоянии «ожидания завершения». Этот ответ имеет соответствующий код.

2 голосов
/ 02 апреля 2011

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

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

...