我为异步类成员编写了一个单元测试。当我在“Debug build”下执行测试时,测试按预期传递。但是,当我在“Release build”下执行我的测试时,它会挂起CPU(当循环死锁时)。
如果我使用Debug构建(即Debug构建单元测试程序集和Release构建目标程序集)专门配置Unit测试项目,那么测试也会通过。
要测试的代码
public override void DoSomething(object parameter)
{
ThreadPool.QueueUserWorkItem(AsyncDoSomething, parameter);
}
private void AsyncDoSomething(object parameter)
{
//Doing something
.....
//Something is done
RaiseSomethingIsDone();
}
我的单元测试
public void DoingSomethingTest()
{
bool IsSomethingDone = false;
//Setup
//Doing some setup here.
target.SomethingDone += (sender, args) =>
{
IsSomethingDone = true;
};
//Exercise
target.DoSomething(_someParameter);
while (!IsSomethingDone ){}
//Verify
//Doing some asserts here.
}
以下是C#编译器在Debug配置和Release配置下生成的IL:
调试while循环IL interpenetration:
IL_00cb: ldloc.s 'CS$<>8__locals7'
IL_00cd: ldfld bool IsSomethingDone
IL_00d2: ldc.i4.0
IL_00d3: ceq
IL_00d5: stloc.s CS$4$0001
IL_00d7: ldloc.s CS$4$0001
IL_00d9: brtrue.s IL_00c9
释放while循环IL interpenetration:
IL_00bc: ldloc.s 'CS$<>8__locals7'
IL_00be: ldfld bool IsSomethingDone
IL_00c3: brfalse.s IL_00bc
我知道有更好的方法可以将测试线程与后台ThreadPool线程同步。
我的问题是
为什么发布版本不起作用?工作线程未设置标志IsSomethingDone。
是因为eventhandler(lambda表达式)没有被执行?
事件是否未正确引发?
顺便说一句,我确认DoSomething正确执行并生成了正确的结果。
跟进问题:
是否应在Debug Build或Release Build下构建单元测试项目?
是否应在Debug Build或Release Build下测试目标程序集?
答案 0 :(得分:8)
标志IsSomethingDone
正缓存在CPU寄存器中,并且永远不会从内存中重新加载,因此当一个线程修改它时,另一个线程永远不会看到修改后的值。
这是.NET JIT编译器所做的优化,因此您需要查看实际的x86 / x64二进制解集以查看此信息,MSIL不够深入: - )
解决方案是将您的旗帜标记为volatile
这是可能发生的一种多线程错误的教科书示例。这么多,这是我在TechEd New Zealand 2012上就这个确切主题所做的演示的一部分的链接。希望它能够更详细地解释发生了什么
http://orionedwards.blogspot.co.nz/2012/09/teched-2012-background-2-compiler.html
正如您所提到的,您不能将volatile
放在本地变量上,但您应该可以使用Thread.VolatileRead
代替。
我建议不要使用这个设计 - 你的主线程将使用100%CPU旋转直到你的工作完成,这太可怕了。更好的解决方案是使用ManualResetEvent
(或其他类型的信令机制)。
这是一个例子
public void DoingSomethingTest()
{
var done = new ManualResetEvent(false); // initially not set
//Setup
//Doing some setup here.
target.SomethingDone += (sender, args) =>
{
done.Set();
};
//Exercise
target.DoSomething(_someParameter);
done.WaitOne(); // waits until Set is called. You can specify an optional timeout too which is nice
//Verify
//Doing some asserts here.
}
答案 1 :(得分:2)
在没有任何锁定(或其他内存障碍)的情况下,我认为IsSomethingDone
需要声明为volatile
。