简体   繁体   中英

why value variable is not changed?

I have a Timer in my winForms and when the TimeSpan is 0 I set done as true and try check the value a thread.

My code:

Initial variables:

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

Timer handling:

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
            {
               //...
            }

        }

Thread callback function:

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

                Thread.Sleep(500); 

            } while (running);
        }

Start timer1_Tick and Thread executions.

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

the problem is that the following statement in .test() method not ever is true ,why?

statement

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

Someone can point my mistake? Thanks in advance.

You have no synchronization protecting the 'done' variable. When an object or variable may be accessed in one thread while another thread is, or might be, modifying it, some form of synchronization is needed.

The ways it can actually fail are many. For example, consider this loop:

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

            Thread.Sleep(500); 

        } while (running);
    }

Suppose the compiler knows that Thread.Sleep does not modify done or running . It may conclude that nothing in this loop modifies done or running (assuming done is false when the loop is entered) and therefore it can cache done (and running !) in registers across calls to Thread.Sleep .

In other words, it can 'optimize' your loop to:

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

Notice that this is a bona fide optimization, if done and running don't have their values changed by other threads. The compiler is free to assume this since it violates the rules to modify a value in one thread while another thread is or might be accessing it without synchronization, this thread might be accessing those variables, and this code contains no synchronization functions.

Of course, your code doesn't have to worry about this if you follow the rules. The compiler is prohibited from making 'optimizations' that break your code and you don't have to worry about how it does it. But that only applies if you follow the rule. (In practice, the compiler will not cache a value in a register across a call to a synchronization function and volatile variables will never be cached in registers.)

As Martin James mentioned, instead of polling flags , you should be using thread synchronization mechanisms.

This simple example shows how you can do this using Monitor.Wait , Monitor.Pulse and lock .

The important difference between this and your code is that this will stop the thread from running until the condition has been met, thus improving the performance of your code.

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

To modify your code to use this mechanism, you need to maintain the reference to the object you used to start the thread (in this example, I will call it 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
        {
           //...
        }
    }

Then, in your test() method, you only need this:

private void test()
    {
        lock(this)
        {
            Monitor.Wait(this); //Will stop the thread until a pulse has been recieved.
            Invoke(new setLabelMethod(setLabelValue), "yeah");
        }
    }

In your timer1_Tick method, you have set running = false; - and your thread is depending on this variable to loop. As far as I can see, this is used to stop the thread once the condition has been met.

You could do this by moving this statement to your thread:

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

            Thread.Sleep(500); 

        } while (running);
    }

This way you ensure that the thread code will be run when the condition is met. What may have been happening is that the thread had already past the if (done) statement when your timer1_Tick code sets the running = false; thus stopping the thread. Remove the running = false; statement from your timer1_Tick method.

Your thread spends 99.9999% of its time sleeping, then it tests the 'running' flag, exits if it is false and then tests the 'done' flag, then sleeps again. The timer handler sets the 'done' flag and then clears the 'running' flag. The chances are overwhelming that, when the timer eventually fires, the thread will find the 'running' flag cleared and so exit before it ever gets to checking the 'done' flag. Try putting a sleep(1000) between setting the 'done' flag and clearing the 'running' flag. Then try a better signaling mechanism than polling flags!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM