EGN
EGN

Reputation: 2572

Not reading the correct property value in multi threaded program

I'm running into a scenario where I'm not sure why am I not reading the correct property value from a property while in multiple threads.. I have written a small console app to illustrate the situation

class Program
{
    private static Test MyObject;
    static void Main(string[] args)
    {
        MyObject = new Test();

        Task.Run(() =>
        {
            var obj = MyObject as Test;
            Console.WriteLine("1: " + obj.MyValue);
            Update("Thread1");
            Console.WriteLine("2: " + obj.MyValue);
        });
        Task.Run(() =>
        {
            var obj = MyObject as Test;
            Console.WriteLine("3: " + obj.MyValue);
            Thread.Sleep(100);
            Update("Thread2");
            Console.WriteLine("4: " + obj.MyValue);
        });

        void Update(string val)
        {
            lock (MyObject)
            {
                MyObject.MyValue = val;
            }
        }

        Thread.Sleep(400);
        Console.WriteLine("5: " + MyObject.MyValue);
    }
}

class Test
{
    public string MyValue { get; set; }
}

Running the above line I'm getting this output

1:
2: Thread1
3:
4: Thread2
5: Thread2

My expectation is "3:" should always say "3: Thread1" because same object was updated earlier. But this is not the case and I'm not sure what am I missing here... Can someone shed some light on this please?

Just a small note... if you run the code and get different order, please re-run it... I'm only interested in order like above.

Upvotes: 1

Views: 91

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70662

A couple of things:

  1. Your Update() method synchronizes writes to the MyValue property, but does nothing about reads. So, the runtime is free to use a cached value for the object. It's unlikely this has anything to do with your observed output, since in practice you're not likely to see this, especially on x86 hardware. But…
  2. More importantly, all that your code demonstrates is that the order of output can be misleading. The Console class has synchronization to ensure coherent output from multiple threads, but there's nothing in the code that ensures that if lines of output are written in a specific order, that the code leading up to those lines of output (such as reading the MyValue property) occurred in the same order that those lines of output are displayed.

In other words, just because the console displayed the "2:" line before the "3:" line, that doesn't actually mean that the call to Update("Thread1") occurred before the call to Console.WriteLine("3: " + obj.MyValue);

You would need to protect the individual operations with a lock statement as well, if you wanted to ensure the lines of output matched the order of execution of statements in the program.

To be more specific, consider the following possible order of execution for your code:

thread 1                                      thread 2
--------                                      --------
                                              "value" parameter <= "3: " + obj.MyValue
Console.WriteLine("1: " + obj.MyValue);
Update("Thread1");
Console.WriteLine("2: " + obj.MyValue);
                                              Console.WriteLine(value);

I.e. it's perfectly legal for the second thread to compute the parameter passed to Console.WriteLine() before the first thread gets around to even starting its logic. But then the first thread can also preempt the second thread before the second thread has a chance to actually call the Console.WriteLine() method.

If that happens, then you see the expected order of output from thread 1, but the output from thread 2 writes an apparently-stale version of the MyValue property, because it was retrieved before thread 1 got to run any of its logic.

To address that specific scenario, you could use lock for the WriteLine() calls. E.g.:

lock (MyObject) Console.WriteLine("1: " + obj.MyValue);

Note that you'd need to put that on every call to Console.WriteLine(). That would ensure that whatever value is written to the console is the most up-to-date value for the property at the time the call was made.

Upvotes: 3

Related Questions