首页 > 解决方案 > 未在多线程程序中读取正确的属性值

问题描述

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

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”,因为之前更新了同一个对象。但事实并非如此,我不确定我在这里错过了什么......有人可以对此有所了解吗?

只是一个小提示...如果您运行代码并获得不同的顺序,请重新运行它...我只对上面的顺序感兴趣。

标签: c#.net

解决方案


有几件事:

  1. 您的Update()方法同步对属性的写入MyValue,但对读取没有任何作用。因此,运行时可以自由地为对象使用缓存值。这不太可能与您观察到的输出有关,因为实际上您不太可能看到这一点,尤其是在 x86 硬件上。但…
  2. 更重要的是,您的代码所展示的只是输出顺序可能会产生误导。该类Console具有同步以确保来自多个线程的一致输出,但代码中没有任何内容可以确保如果以特定顺序编写输出行,则导致这些输出行的代码(例如读取MyValue属性)发生与显示这些输出行的顺序相同。

换句话说,仅仅因为控制台"2:"在该行之前显示了该"3:"行,这实际上并不意味着调用 toUpdate("Thread1")发生在调用 to 之前Console.WriteLine("3: " + obj.MyValue);

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

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

线程 1 线程 2
-------- --------
                                              “值”参数 <= “3:” + obj.MyValue
Console.WriteLine("1:" + obj.MyValue);
更新(“线程1”);
Console.WriteLine("2:" + obj.MyValue);
                                              Console.WriteLine(值);

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

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

要解决该特定情况,您可以使用lock调用WriteLine()。例如:

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

请注意,您需要在每次调用Console.WriteLine(). 这将确保写入控制台的任何值都是调用时属性的最新值。


推荐阅读