我正在遇到一种情况,我不确定为什么在多个线程中不能从属性中读取正确的属性值。我编写了一个小型控制台应用程序来说明这种情况
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”,因为同一个对象之前已更新。但这不是事实,我不确定这里我想念的是什么...有人可以请问一下吗?
仅需注意一点...如果您运行代码并得到不同的命令,请重新运行...我只对上面的命令感兴趣。
答案 0 :(得分:3)
几件事:
Update()
方法将 writes 同步到MyValue
属性,但是对 reads 无效。因此,运行时可以自由使用对象的缓存值。这不太可能与您观察到的输出有任何关系,因为在实践中,您不太可能看到这一点,尤其是在x86硬件上。但是... 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()
上添加该内容。这样可以确保在进行调用时,写入控制台的任何值都是该属性的最新值。