почему значение переменной не изменено? - PullRequest
1 голос
/ 02 ноября 2011

У меня есть Timer в моих winForms, и когда TimeSpan равно 0, я устанавливаю done как true и пытаюсь проверить значение потока.

Мой код:

Исходные переменные:

private DateTime endTime = DateTime.UtcNow.AddMinutes(1);
private bool done;
private bool running = true;
private int count = 0;
private delegate void setLabelMethod(string msg);
private void setLabelValue(string msg) { label2.Text = msg; }

Обработка таймера:

private void timer1_Tick_1(object sender, EventArgs e)
        {
            TimeSpan remainingTime = endTime - DateTime.UtcNow;

            if (remainingTime <= TimeSpan.Zero)
            {
                label1.Text = "Done!";
                timer1.Enabled = false;
                done = true;
                running = false;
            }
            else
            {
               //...
            }

        }

Функция обратного вызова потока:

private void test()
        {
            do
            {
                if (done)
                {
                    Invoke(new setLabelMethod(setLabelValue), "yeah");
                    done = false;
                }

                Thread.Sleep(500); 

            } while (running);
        }

Запустить timer1_Tick и Thread выполнения.

 private void button2_Click(object sender, EventArgs e)
        {
            Thread th = new Thread(new ThreadStart(test));
            th.Start();
            timer1.Enabled = true;
        }

проблема в том, что следующий оператор в методе .test() не всегда равен true, почему?

заявление

 if (done)
 {
    Invoke(new setLabelMethod(setLabelValue), "yeah");
    done = false;
 }

Кто-то может указать мою ошибку?Заранее спасибо.

Ответы [ 4 ]

2 голосов
/ 02 ноября 2011

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

Есть много способов, которыми это может закончиться. Например, рассмотрим этот цикл:

private void test()
    {
        do
        {
            if (done)
            {
                Invoke(new setLabelMethod(setLabelValue), "yeah");
                done = false;
            }

            Thread.Sleep(500); 

        } while (running);
    }

Предположим, что компилятор знает, что Thread.Sleep не изменяет done или running. Можно сделать вывод, что ничто в этом цикле не изменяет done или running (при условии, что done является ложным при входе в цикл), и поэтому оно может кэшировать donerunning!) В регистрах через вызовы Thread.Sleep.

Другими словами, он может «оптимизировать» ваш цикл до:

private void test()
    {
       if (done)
           Invoke(new setLabelMethod(setLabelValue), "yeah");
       done = false;
       if (running)
          while(1) Thread.Sleep(500); 
    }

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

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

1 голос
/ 02 ноября 2011

Как отметил Мартин Джеймс, вместо флагов опроса вы должны использовать Поток синхронизации Механизмы.

Этот простой пример показывает, как вы можете сделать это, используя Monitor.Wait, Monitor.Pulse и lock.

Важное различие между этим и вашим кодом состоит в том, что это остановит выполнение потока до тех пор, пока условие не будет выполнено, таким образом улучшит производительность твой код.

class Program
{
    static void Main(string[] args)
    {
        ThreadExample example = new ThreadExample();
        Thread thread = new Thread(example.Run);

        Console.WriteLine("Main: Starting thread...");
        thread.Start();

        Console.WriteLine("Press a key to send a pulse");
        Console.ReadKey();

        lock (example) //locks the object we are using for synchronization
        {
            Console.WriteLine("Sending pulse...");
            Monitor.Pulse(example); //Sends a pulse to the thread
            Console.WriteLine("Pulse sent.");
        }
        thread.Join();

        Console.ReadKey();
    }
}

class ThreadExample
{
    public void Run()
    {
        Console.WriteLine("Thread: Thread has started");
        lock (this) //locks the object we are using for synchronization
        {
            Monitor.Wait(this); //Waits for one pulse - thread stops running until a pulse has been sent
            Console.WriteLine("Thread: Condition has been met");
        }
    }
}

Чтобы изменить ваш код для использования этого механизма, вам нужно сохранить ссылку на объект, который вы использовали для запуска потока (в этом примере я назову его threadObject)

private void timer1_Tick_1(object sender, EventArgs e)
    {
        TimeSpan remainingTime = endTime - DateTime.UtcNow;

        if (remainingTime <= TimeSpan.Zero)
        {
            label1.Text = "Done!";
            timer1.Enabled = false;
            lock(threadObject){
                Monitor.Pulse(threadObject); //You signal the thread, indicating that the condition has been met
            }
        }
        else
        {
           //...
        }
    }

Тогда, в вашем test() методе, вам нужно только это:

private void test()
    {
        lock(this)
        {
            Monitor.Wait(this); //Will stop the thread until a pulse has been recieved.
            Invoke(new setLabelMethod(setLabelValue), "yeah");
        }
    }
0 голосов
/ 02 ноября 2011

Ваш поток проводит 99,9999% времени в спящем режиме, затем он проверяет флаг «выполнения», завершает работу, если он имеет значение «ложь», а затем проверяет флаг «Готово», а затем снова спит.Обработчик таймера устанавливает флаг «выполнено», а затем очищает флаг «работает».Вероятность того, что когда таймер в конце концов сработает, поток обнаружит, что флаг выполнения сброшен, и завершит работу, прежде чем он сможет проверить флаг «выполнено».Попробуйте установить режим сна (1000) между установкой флага «Выполнено» и сбросом флага «Выполнение».Тогда попробуйте лучший механизм сигнализации, чем флаги опроса!

0 голосов
/ 02 ноября 2011

В вашем методе timer1_Tick вы установили running = false; - и ваш поток зависит от этой переменной для цикла. Насколько я вижу, это используется для остановки потока после выполнения условия.

Вы можете сделать это, переместив этот оператор в свою ветку:

private void test()
    {
        do
        {
            if (done)
            {
                Invoke(new setLabelMethod(setLabelValue), "yeah");
                done = false;
                running = false;
            }

            Thread.Sleep(500); 

        } while (running);
    }

Таким образом вы гарантируете, что код потока будет запущен при выполнении условия. Возможно, произошло то, что поток уже прошел оператор if (done), когда ваш код timer1_Tick устанавливает running = false;, тем самым останавливая поток. Удалите оператор running = false; из вашего метода timer1_Tick.

...