Question

I've been hitting my head with this for the past few hours, so here goes. Maybe it's a common mistake for someone with little experience with multi-threading? Who knows.

In the included code I instantiate 3 threads that run method DisplayValues(DateTime Now, int Period). The debugger stops three times in each of the if statements and, for each, it does the method call with correct values. The problem is that Console.WriteLine is displaying erratic values, completely different to how the calls were made.

The console calls DisplayValues() 3 times with the following parameters, which is correct: DisplayValues('{5/8/2014 4:20:00 AM}', 0); DisplayValues('{5/8/2014 4:35:00 AM}', 1); DisplayValues('{5/8/2014 4:50:00 AM}', 2);

But the output is completely different:

5/8/2014 4:35:00 AM Period: 0

5/8/2014 4:50:00 AM Period: 1

5/8/2014 4:51:00 AM Period: 2

The debugger confirms this. Since it's a console application, I thought it could be that all methods were static, so I moved DisplayValues() to a class. Then I thought that all three class instances had the same name, so I changed the name. Then I thought it could be the CancellationTokenSource object, so I removed that also.

Needless to say, without the threads the output is correct.

I know there's an obvious reason, I just don't know what it is.

Any help is appreciated. Thanks.

bool thread0Running = false;
bool thread1Running = false;
bool thread2Running = false;
DateTime DateNow = new DateTime(2014, 5, 8, 4, 0, 0);

while ((!thread0Running || !thread1Running || !thread2Running) && DateNow.Hour == 4)
{
    if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 20))
    {
        thread0Running = true;
        Class myClass0 = new Class();
        new Thread(() => myClass0.DisplayValues(DateNow, 0, cts0.Token)).Start();

    }
    else if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 35))
    {
        thread1Running = true;
        Class myClass1 = new Class();
        new Thread(() => myClass1.DisplayValues(DateNow, 1, cts1.Token)).Start(); 
    }
    else if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 50))
    {
        thread2Running = true;
        Class myClass2 = new Class();
        new Thread(() => myClass2.DisplayValues(DateNow, 2, cts2.Token)).Start();
    }
    DateNow = DateNow.AddMinutes(1);
}
public void DisplayValues(DateTime Now, int Period, Object obj)
{
        Console.WriteLine(Now.ToString() + " Period: " + Period.ToString());
}
Était-ce utile?

La solution

Thread.Start doesn't mean the thread starts running immediately, it Causes the operating system to change the state of the current instance to ThreadState.Running. Once a thread is in the ThreadState.Running state, the operating system can schedule it for execution, but it doesn't means the thread created first will be executed first. This is the reason of the problem.

If you want to make the 3 thread running in sequence, you should look into thread synchronization.

Autres conseils

As already pointed out by others, the Console.WriteLine might be slower than the increase of the variable. Another approach to solve this, would be to use thread-local variables. They should not be affected by changes in other threads. For C# I found this link: http://msdn.microsoft.com/en-us/library/dd642243(v=vs.110).aspx

The advantages of this approach, you don't have to maintain as many variables as threads and the work of the threads can be done simultaneously.

good luck!

I think the reason is that the main thread changing DateNow at the same time that other thread reading that same value.

Consider this: one of the conditions is true - so a new thread is created, but you have no control when that thread is going to be scheduled to run. so in the meanwhile - your main thread changed DateNow... so.. when the newly created thread is actually run - the value which he sees and prints - is different from the "sane" value, which passed the condition...

Consider this even more bizarre stuation: C# provides atomicy when writing variables of 32bit or less

Atomicy means (in very general and inaccurate way) that the operation cannot be interrupted in the middle... All threads get some CPU, then the OS stops it, and schedules another thread to run.. and some time after that - the OS will schedule that our thread again, and it will continue from where it stopped. An atomic operation cannot be stopped in the middle... it either haven't started yet, or it completed already.

But DateTime is actually 64 bits.. that's mean that the OS can interrupt you main thread - in the middle of writing its new value. that means that until the main thread is scheduled again - DateNow will have some weird, inconsistent value - and any of the other thread can be reading that value in the meanwhile.

Since you are using a lambda expression for your thread function, the value of DateNow is not copied until sometime after thread execution starts. Since you have no synchronization between the threads, this is totally unpredictable. The first thread (Period 0) probably does not get any cpu until you start to create the second thread and then displays the current value of DateNow (4:35). The same happens with Period 1, and then Period 2 finally get to run once you come around the loop again after adding 1 minute. Switch each lambda to use its own variable like this:

if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 20))
{
    DateTime DateNow0 = DateNow; // DAteTime is a struct so this is a value copy
    thread0Running = true;
    Class myClass0 = new Class();
    new Thread(() => myClass0.DisplayValues(DateNow0, 0, cts0.Token)).Start();

}

Use DateNow1 and DateNow2 it the other 2 blocks. With that change I think you will then get the output you are expecting. The copy of the current value of DateNow now takes place at a predictable place in you main execution thread. However, the order may still not come out correctly as there is no guarantee that the three threads will run in the order they are created.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top