Fra*_*Liu 5 c# multithreading unit-testing
我为异步类成员编写了一个单元测试.当我在"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();
}
Run Code Online (Sandbox Code Playgroud)
我的单元测试
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.
}
Run Code Online (Sandbox Code Playgroud)
这是在调试配置和发布配置下由C#编译器生成的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
Run Code Online (Sandbox Code Playgroud)
释放while循环IL互穿:
IL_00bc: ldloc.s 'CS$<>8__locals7'
IL_00be: ldfld bool IsSomethingDone
IL_00c3: brfalse.s IL_00bc
Run Code Online (Sandbox Code Playgroud)
我知道有更好的方法可以将测试线程与后台ThreadPool线程同步.
我的问题是
为什么发布版本不起作用?工作线程未设置标志IsSomethingDone.
是因为eventhandler(lambda表达式)没有被执行?
事件没有正确提出吗?
顺便说一句,我确认DoSomething正确执行并生成了正确的结果.
跟进问题:
是否应该在Debug Build或Release Build下构建单元测试项目?
应该在Debug Build或Release Build下测试目标程序集吗?
该标志IsSomethingDone正缓存在CPU寄存器中,并且永远不会从内存中重新加载,因此当一个线程修改它时,另一个线程永远不会看到修改后的值.
这是.NET JIT编译器所做的一个优化,所以你需要查看实际的x86/x64二进制dissasembly才能看到这个,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.
}
Run Code Online (Sandbox Code Playgroud)