Join отказывается признать, что дочерний поток завершился после того, как свойство IsAlive имеет значение false. C # - PullRequest
4 голосов
/ 21 июня 2011

Nutshell: я запускаю поток из своей формы, затем через некоторое время использую метод Join. Это заканчивается, но мое приложение застряло в Присоединении и отказывается признать, что оно закончило присоединение. Что могло бы вызвать это? Моя ветка запускается с помощью кнопки в моей форме и пытается присоединиться ко второй кнопке в той же форме.

Подробнее: У меня есть приложение, которое использует многопоточность для осуществления связи и обработки чисел. Предполагая, что основная форма является родительским потоком, первым дочерним элементом является Child1. После запуска Child1 устанавливает некоторые связи с внешними устройствами и запускает 2 собственных дочерних потока (Child2 и Child3) для обработки входящих данных.

Когда пользователь решает, что приложение должно прекратить обработку входящих данных, мне нужно, чтобы Child1 завершил работу (чтобы настройки com можно было изменить перед возобновлением, если это необходимо). Я установил событие остановки, и Child1 выходит из своего цикла выполнения, первым делом он уведомляет Child2 и Child3, что они больше не нужны (через другое событие остановки), Child2 и Child3 ожидаются с помощью метода Join в Child1. Это работает просто отлично.

Что не работает, так это то, что форма также использует метод Join для Child1 после установки события остановки, которое побуждает Child1 выйти из цикла выполнения и завершить, это соединение, однако, ждет бесконечно.

Пошаговое руководство. При просмотре приложения я замечаю, что перед использованием Join свойство IsAlive имеет значение true. После того, как я нажал Child1.Join (), я больше не могу получать какую-либо информацию из потока, потому что он находится в "JoinWaitSleep". Однако, если я запускаю цикл while, который заставляет поток формы спать, пока Child1.IsAlive имеет значение true, это работает просто отлично. Является ли моя вторая кнопка частью потока, который не может присоединить к ней Child1?


public void Run()
{   //Known as Child1
    //code to setup coms is here

    //Launch Builder threads
    InsertBackgroundMonitor("Launching Collector Threads");
    RunBuilders = true;

    //Known as Child2 and Child3
    BuildThread1 = new Thread(new ThreadStart(Cam1Builder));
    BuildThread2 = new Thread(new ThreadStart(Cam2Builder));
    BuildThread1.Start();
    BuildThread2.Start();

    while (!StopEventHandle.WaitOne(0, true))
    {
    //// Code that waits for coms and tosses data into lists
    }

    RunBuilders = false;

    //Wait for threads to terminate
    BuildThread1.Join();
    BuildThread2.Join();
}

private void RunButton_Click(object sender, System.EventArgs e)
{
    //Button for running the control thread
    ControlThread = new Thread(new ThreadStart(Run));
    ControlThread.Start();
}

private void StopButton_Click(object sender, System.EventArgs e)
{
    if (btnStop.Enabled)
    {   //Button for stopping the control thread
        StopEventHandle.Set();

        if (ControlThread != null)
        {
            while (ControlThread.IsAlive)
            {
                Thread.Sleep(100);
            }
            //somehow Join did not work
            //ControlThread.Join();
        }

        //Update buttons
        btnStart.Enabled = true;
        btnStop.Enabled = false;               
    }
}

Ответы [ 3 ]

2 голосов
/ 21 июня 2011

Сложно сказать без кода, но у вас есть НЕКОТОРЫЙ ресурс, который вы запустили, и который вы не закрыли должным образом.

Join () ожидает полного закрытия потока и освобождения всех ресурсов перед возвратом. Если у потока есть какие-либо задачи BackgroundWorker, или если у него есть какие-либо запасные задачи, которые вы не показываете, все еще выполняющиеся, он не вернется.

Поскольку и BuildThread1, и BuildThread2 возвращаются правильно, и Join'ing, вы можете быть уверены, что это не один из них или что-то еще, что они делают. Посмотрите на остальную часть кода. Что это делает?

EDIT: Это отлично работает:

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }


    Thread ControlThread;
    Thread BuildThread1;
    Thread BuildThread2;
    volatile bool RunBuilders = true;
    volatile bool RunControl = true;

    private void button1_Click(object sender, EventArgs e)
    {
        ControlThread = new Thread(new ThreadStart(Run));
        ControlThread.Start();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        RunControl = false;

        if (ControlThread != null)
        {
            while (ControlThread.IsAlive)
            {
                Thread.Sleep(100);
            }
            //somehow Join did not work
            ControlThread.Join();
        }
    }

    public void Run()
    {   //Known as Child1
        //code to setup coms is here

        //Launch Builder threads
        RunBuilders = true;

        //Known as Child2 and Child3
        BuildThread1 = new Thread(new ThreadStart(Cam1Builder));
        BuildThread2 = new Thread(new ThreadStart(Cam1Builder));
        BuildThread1.Start();
        BuildThread2.Start();

        while (RunControl)
        {
        //// Code that waits for coms and tosses data into lists
        }

        RunBuilders = false;

        //Wait for threads to terminate
        BuildThread1.Join();
        BuildThread2.Join();
    }


    public void Cam1Builder()
    {
        while ( RunBuilders )
        {

        }
    }
}
}
1 голос
/ 21 июня 2011

Вы пытались изменить RunBuilders на volatile?Лично я не знаю, так как я бы использовал bool таким образом, кросс-потоки, но если вы собираетесь использовать его, он должен быть изменчивым, чтобы все потоки видели обновление вместо устаревшего значения, «кэшированного» JIT.

Кроме этого, нужно посмотреть, что делают два других потока.

1 голос
/ 21 июня 2011

Я могу только догадываться о возможных проблемах, которые могут вызвать это, поскольку я не вижу кода для методов Cam1Builder и Cam2Builder. 1 Возможности, которые сразу же появляются у меня:

  • Вызов Join блокирует выполнение вызывающего потока. Если это сделано из потока пользовательского интерфейса, он останавливает рассылку сообщений. Если работник пытается публиковать сообщения в потоке пользовательского интерфейса (например, через Control.Invoke), то оба потока будут тупиковыми.
  • Если RunBuilders не помечен как volatile или не доступен внутри lock, то его значение, воспринимаемое другими потоками, не может быть предсказано в любой данный момент времени.

В общем случае не рекомендуется вызывать Join из потока пользовательского интерфейса. Потоки пользовательского интерфейса отличаются тем, что выполняют неопределенный цикл, который отправляет и обрабатывает сообщения Windows. Этот цикл инициируется вызовом Application.Run, который вы должны увидеть где-то в своем коде. Если вы заблокируете поток пользовательского интерфейса, вызвав Join или другой метод блокировки, он не сможет обрабатывать сообщения в очереди. Возможно, вы используете Control.Invoke, чтобы маршалировать выполнение делегата в потоке пользовательского интерфейса из одного из рабочих потоков. Если это так, то оба потока будут тупиковыми. Join блокирует поток пользовательского интерфейса, ожидающий рабочий поток. Control.Invoke блокирует рабочий поток, ожидающий поток пользовательского интерфейса.

Переменные, используемые в качестве механизмов связи между потоками, должны быть защищены от потоков. В вашем случае вы используете RunBuilders в качестве механизма для оповещения о действии в рабочем потоке. Проблема в том, что, поскольку RunBuilders не помечен как volatile и не доступен из блока lock, его значение не может быть надежно передано в рабочий поток. Возможно, вы устанавливаете его значение на true в одном потоке, но другой поток может продолжать читать false бесконечно.


1 Если вы разместите больше кода, я смогу обеспечить лучшее понимание. В частности, я хотел бы увидеть общую схему того, что Cam1Builder или Cam2Builder делает особенно части, где он читает RunBuilders.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...