[英]Unit test passes in debug build but fails in release build
I have written a unit test for an asynchronous class member. 我为异步类成员编写了一个单元测试。 The test passes as expected when I execute my test under "Debug build".
当我在“Debug build”下执行测试时,测试按预期传递。 However, it hangs the CPU (while loop is deadlocked) when I execute my test under "Release build".
但是,当我在“Release build”下执行我的测试时,它会挂起CPU(当循环死锁时)。
If I specifically configure the Unit test project with Debug build (that is Debug build unit test assembly and Release build target assembly), the test passes as well. 如果我使用Debug构建(即Debug构建单元测试程序集和Release构建目标程序集)专门配置Unit测试项目,那么测试也会通过。
Code to Test 要测试的代码
public override void DoSomething(object parameter)
{
ThreadPool.QueueUserWorkItem(AsyncDoSomething, parameter);
}
private void AsyncDoSomething(object parameter)
{
//Doing something
.....
//Something is done
RaiseSomethingIsDone();
}
My Unit Test 我的单元测试
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.
}
Here is the IL generated by the C# compiler under Debug configuration and Release configuration: 这是在调试配置和发布配置下由C#编译器生成的IL:
Debug while loop IL interpenetration: 调试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
Release while loop IL interpenetration: 释放while循环IL互穿:
IL_00bc: ldloc.s 'CS$<>8__locals7'
IL_00be: ldfld bool IsSomethingDone
IL_00c3: brfalse.s IL_00bc
I understand there are better ways to synchronize the test thread to the background ThreadPool thread. 我知道有更好的方法可以将测试线程与后台ThreadPool线程同步。
My questions are 我的问题是
why is the release build not working? 为什么发布版本不起作用? The flag IsSomethingDone is not set by the worker thread.
工作线程未设置标志IsSomethingDone。
Is it because the eventhandler (the lambda expression) does not get executed? 是因为eventhandler(lambda表达式)没有被执行?
Is the event not raised correctly? 事件没有正确提出吗?
By the way, I verified that DoSomething is executed correctly and generated the correct result. 顺便说一句,我确认DoSomething正确执行并生成了正确的结果。
Follow up Questions: 跟进问题:
Should one build unit test projects under Debug Build or Release Build ? 是否应该在Debug Build或Release Build下构建单元测试项目?
Should one test the target assembly under Debug Build or Release Build ? 应该在Debug Build或Release Build下测试目标程序集吗?
The flag IsSomethingDone
is being cached in a CPU register, and never reloaded from memory, so that when one thread modifies it, the other never sees the modified value. 标志
IsSomethingDone
正在高速缓存在CPU寄存器中,并且永远不会从内存重新加载,因此当一个线程修改它时,另一个线程永远不会看到修改后的值。
This is an optimization which the .NET JIT compiler does, so you'd need to look at the actual x86/x64 binary dissasembly to see this, MSIL is not deep enough :-) 这是.NET JIT编译器所做的一个优化,所以你需要查看实际的x86 / x64二进制dissasembly才能看到这个,MSIL不够深入:-)
The solution is to mark your flag as volatile
解决方案是将您的标记标记为
volatile
This is a textbook example of one of the kind of multithreading bugs that can occur. 这是可能发生的一种多线程错误的教科书示例。 So much so, that here's a link to part of a presentation I did at TechEd New Zealand 2012 on this exact subject.
这么多,这是我在TechEd New Zealand 2012上就这个确切主题所做的演示的一部分的链接。 Hopefully it should explain what's going on in more detail
希望它能够更详细地解释发生了什么
http://orionedwards.blogspot.co.nz/2012/09/teched-2012-background-2-compiler.html http://orionedwards.blogspot.co.nz/2012/09/teched-2012-background-2-compiler.html
As you have mentioned, you can't put volatile
on a local variable, but you should be able to use Thread.VolatileRead
instead. 正如您所提到的,您不能将
volatile
放在局部变量上,但您应该能够使用Thread.VolatileRead
。
I'd suggest not using this design at all - your main thread is going to spin using 100% CPU until your worker completes, which is horrible. 我建议根本不使用这个设计 - 你的主线程将使用100%CPU旋转,直到你的工人完成,这太可怕了。 A better solution is to use a
ManualResetEvent
(or other kind of signaling mechanism). 更好的解决方案是使用
ManualResetEvent
(或其他类型的信令机制)。
Here's an example 这是一个例子
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.
}
在没有任何锁定(或其他内存屏障)的情况下,我认为IsSomethingDone
需要声明为volatile
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.