首页 > 解决方案 > 在 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

标签: c#.net

解决方案


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

另一方面,如果您有一组并行运行的任务,那么如果您不断更新同一个对象,您将无法保持数据的一致性。

以下示例具有三个过程...

  1. 您已经实施的流程,等待所有执行完成
  2. 编译任务列表,然后使用随机数一起执行它们。注意:如果您执行的迭代次数超过 5 次,您会注意到元素的数量开始增长,但无论 Data 在 Task 执行时的值是什么,都会更新。有时,它会覆盖同时执行的另一个Task的数据。本节会发生这种情况,因为在将值分配给 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

推荐阅读