Alex Chrostowski
Alex Chrostowski

Reputation: 108

In C# is it guaranteed that any given thread will see updates made, from yet another thread, to the value of a variable of reference type?

I'm a little confused about how I guarantee that any given thread will see updates made, from yet another thread, to the value of a variable of reference type.

For example, if I have the following class:

public class DumbClass
{
  public ImmutableList<int> Data { get; set; } = ImmutableList<int>.Empty;
}

And run the following program:

   public static class Program
   {
      public static async Task Main()
      {
         var dumbClass = new DumbClass();
         var numTasks = 5;

         for (var i = 0; i < numTasks; i++)
            await Task.Run(() =>
            {
               dumbClass.Data = dumbClass.Data.Add(i);
               Console.WriteLine($"Data: {string.Join(',', dumbClass.Data)}");
            });
      }
   }

Am I always guaranteed to get the following output?

Data: 0
Data: 0,1
Data: 0,1,2
Data: 0,1,2,3
Data: 0,1,2,3,4

Or is it possible that the update to dumbClass.Data is not yet visible to a certain thread by the time it starts executing, so you get something like this? I can't decide if this scenario is even possible, and if it is, what is the best way to avoid it, given the example above.

Data: 0
Data: 1
Data: 1,2
Data: 1,2,3
Data: 1,2,3,4

Upvotes: 2

Views: 173

Answers (1)

Jawad
Jawad

Reputation: 11364

as many of the comments have already mentioned, the way you call the thread/Task is what will guarantee the safety of your data. If you are calling await before updating the data of same object in the next iteration, you are guaranteed because the program will "wait" for the execution to finish before proceeding to the next one.

On the other hand, if you have a collection of tasks that you run in parallel, there is no way you will have consistency in the data if you continuously update the same object.

Following example has three processes...

  1. Process you have implemented already, await until all executions are done
  2. Compile a list of tasks and then execute them all together using random numbers. NOTE: If you do more iterations than 5, you'll note that number of elements start growing but still, whatever the value of Data was at the time of Task execution, will be updated. Sometimes, it will overwrite the data of another Task executing at the same time. It happens with this section because each of these are evaluating and taking some cpu time to get a random number before assigning value to Data and updating dumpClass.Data. (Run it with numTasks * 3 in the for loops).
  3. Compile a list of tasks that use the value from the loop iterator i.
// Wait for each task to finish before moving on.
var dumbClass = new DumbClass();
var numTasks = 5;

for (var i = 0; i < numTasks; i++)
    await Task.Run(() =>
    {
        dumbClass.Data = dumbClass.Data.Add(i);
        Console.WriteLine($"Data: {string.Join(',', dumbClass.Data)}");
    });

// Run all tasks together with random value each time.
dumbClass = new DumbClass();
List<Task> tasks = new List<Task>();
for (var i = 0; i < numTasks * 2; i++) // Twice to see the proper results.
    tasks.Add(new Task(() => 
    { 
        dumbClass.Data = dumbClass.Data.Add(new Random().Next(0, 10)); 
        Console.WriteLine($"XData: {string.Join(',', dumbClass.Data)}");        
    }));

Parallel.ForEach<Task>(tasks, (t) => { t.Start(); });
Task.WaitAll(tasks.ToArray());

// Run all tasks together with value of i.
dumbClass = new DumbClass();
tasks = new List<Task>();
for (var i = 0; i < numTasks; i++)
    tasks.Add(new Task(() =>
    {
        dumbClass.Data = dumbClass.Data.Add(i);
        Console.WriteLine($"IData: {string.Join(',', dumbClass.Data)}");
    }));
Parallel.ForEach<Task>(tasks, (t) => { t.Start(); });
Task.WaitAll(tasks.ToArray());

Output shows you what will happen

Data: 0
Data: 0,1
Data: 0,1,2
Data: 0,1,2,3
Data: 0,1,2,3,4

XData: 8
XData: 4
XData: 5
XData: 3
XData: 2
XData: 5
XData: 4
XData: 5,5
XData: 5,5,1
XData: 5,5,1,5

IData: 5
IData: 5,5
IData: 5,5,5
IData: 5,5,5,5,5
IData: 5,5,5,5

Upvotes: 2

Related Questions