考虑代码:
class InnerClass
{
public int State { get; set; }
public bool Equals(InnerClass other) => State == other.State;
public override int GetHashCode() => State;
public override string ToString() => State.ToString();
}
class TestClass
{
private static InnerClass _innerClass;
private static readonly object _syncObject = new object();
public InnerClass Get()
{
lock (_syncObject)
{
return _innerClass;
}
}
public void Set(InnerClass innerClass)
{
lock (_syncObject)
{
_innerClass = innerClass;
}
}
}
[Fact]
public void TestClassTest()
{
var firstInnerClass = new InnerClass() {State = 1};
var secondInnerClass = new InnerClass() {State = 2};
Assert.NotEqual(firstInnerClass, secondInnerClass);
for (int i = 0; i < 100000; i++)
{
var testClass = new TestClass();
testClass.Set(firstInnerClass);
var currentInnerClass = testClass.Get();
Task.Run(() => testClass.Set(secondInnerClass));
Assert.Equal(firstInnerClass, currentInnerClass);
}
}
它不起作用。我知道它与_innerClass
字段有关。看起来currentInnerClass
指针可以在以后替换。
但为什么下一个代码可以正常工作呢?
class TestClass
{
private static InnerClass _innerClass;
public InnerClass Get()
{
return Interlocked.CompareExchange(ref _innerClass, _innerClass, _innerClass);
}
public void Set(InnerClass innerClass)
{
Interlocked.CompareExchange(ref _innerClass, innerClass, null);
}
}
所以有两个问题:
Interlocked如何与内存一起工作以便它可以正常工作?
如何更改第一个代码(使用lock
运算符)以使其正常?也许MemoryBarrier可以帮助我?我不确定......
答案 0 :(得分:4)
让我们看一下你在循环中所做的事情:
// Here you are creating a new TestClass. Should be whole new state, right
//Maybe not...
var testClass = new TestClass();
// Here we set the field to firstInnerClass
testClass.Set(firstInnerClass);
// Here we get the field value. Should be firstInnerClass always, right? Maybe...
var currentInnerClass = testClass.Get();
// Here we create a task to run on another thread.
// This will asynchronously update the state.
// Might take a while though...
Task.Run(() => testClass.Set(secondInnerClass));
// Here we assert that the state we read is what we set
Assert.Equal(firstInnerClass, currentInnerClass);
到目前为止,这么好。我们写一个值,我们读取一个值,我们异步地启动对该值的更改,然后我们确认我们读到的是我们写的内容。
问题在于:
private static InnerClass _innerClass;
static
这里表示该字段与类型相关联,而不是与实例相关联。它在所有类的实例中共享。所以正在发生的是,在某些迭代N中,来自迭代N-1的异步任务花费足够长的时间来完成它在写和读之间执行它的更新。因此,读取得到secondInnerClass
,而不是firstInnerClass
。您正在创建新的TestClass的事实是无关紧要的 - 该类的内容字段是静态的,因此与其他所有副本共享。
如果将TestClass更改为以下内容,则错误应消失:
class TestClass
{
private InnerClass _innerClass;
private readonly object _syncObject = new object();
public InnerClass Get()
{
lock (_syncObject)
{
return _innerClass;
}
}
public void Set(InnerClass innerClass)
{
lock (_syncObject)
{
_innerClass = innerClass;
}
}
}