在 C# 中,是否保证任何给定线程都会看到从另一个线程对引用类型变量的值所做的更新?



我对如何保证任何给定线程将看到从另一个线程到引用类型变量值的更新有点困惑。

例如,如果我有以下类:

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

并运行以下程序:

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

我是否总是保证获得以下输出?

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

或者,当某个线程开始执行时,对dumbClass.Data的更新是否还不可见,所以你会得到这样的东西? 鉴于上面的例子,我无法确定这种情况是否可能,如果是,避免这种情况的最佳方法是什么。

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

正如许多评论已经提到的,您调用线程/任务的方式将保证数据的安全性。如果在下一次迭代中更新同一对象的数据之前调用 await,则可以保证,因为程序将"等待"执行完成,然后再继续下一次迭代。

另一方面,如果您有一个并行运行的任务集合,那么如果您不断更新同一对象,则数据不可能保持一致。

以下示例有三个进程...

  1. 您已经实现的过程,等待所有执行完成
  2. 编译任务列表,然后使用随机数一起执行它们。注意:如果执行的迭代次数超过 5 次,您会注意到元素的数量开始增长,但无论执行任务时 Data 的值如何,都会更新。有时,它会覆盖同时执行的另一个任务的数据。它发生在本节中,因为其中每个都在评估并花费一些 CPU 时间来获取随机数,然后再为数据分配值并更新dumpClass.Data。(在 for 循环中使用 numTasks * 3 运行它(。
  3. 编译使用循环迭代器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());

输出显示将会发生什么

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

最新更新