在多线程程序中无法读取正确的属性值

时间:2019-07-15 16:15:32

标签: c# .net

我正在遇到一种情况,我不确定为什么在多个线程中不能从属性中读取正确的属性值。我编写了一个小型控制台应用程序来说明这种情况

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

运行上面的行,我得到此输出

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

我的期望是“ 3:”应该总是说“ 3:Thread1”,因为同一个对象之前已更新。但这不是事实,我不确定这里我想念的是什么...有人可以请问一下吗?

仅需注意一点...如果您运行代码并得到不同的命令,请重新运行...我只对上面的命令感兴趣。

1 个答案:

答案 0 :(得分:3)

几件事:

  1. 您的Update()方法将 writes 同步到MyValue属性,但是对 reads 无效。因此,运行时可以自由使用对象的缓存值。这不太可能与您观察到的输出有任何关系,因为在实践中,您不太可能看到这一点,尤其是在x86硬件上。但是...
  2. 更重要的是,您的代码演示的所有内容都是 output 的顺序可能会引起误解。 Console类具有同步功能,以确保多个线程的输出一致,但是代码中没有任何内容可以确保如果以特定顺序编写输出行,则导致到达这些输出行的代码(例如读取MyValue属性)的顺序与显示这些输出行的顺序相同。

换句话说,仅仅是因为 console "2:"行之前显示了"3:"行,这实际上并不意味着对Update("Thread1")的调用发生在调用Console.WriteLine("3: " + obj.MyValue);

之前

如果要确保输出行与程序中语句的执行顺序相匹配,则还需要使用lock语句来保护各个操作。

更具体地说,请考虑以下可能的代码执行顺序:

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

即在第一个线程开始执行逻辑之前,第二个线程计算传递给Console.WriteLine()的参数是完全合法的。但是,第一个线程也可以在第二个线程有机会真正调用 Console.WriteLine()方法之前抢占第二个线程。

如果发生这种情况,那么您将看到线程1的预期输出顺序,但是线程2的输出将写入MyValue属性的明显过时的版本,因为它是在线程1运行之前被检索的任何逻辑。

要解决特定于 的情况,可以将lock用于WriteLine()调用。例如:

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

请注意,您需要在每次 调用Console.WriteLine()上添加该内容。这样可以确保在进行调用时,写入控制台的任何值都是该属性的最新值。