Reputation: 108
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
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...
dumpClass.Data
. (Run it with numTasks * 3 in the for loops).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