C# 单元测试在调试生成中通过,但在发布生成中失败
我已经为一个异步类成员编写了一个单元测试。当我在“调试构建”下执行测试时,测试按预期通过。然而,当我在“发布构建”下执行测试时,它会挂起CPU(而循环处于死锁状态) 如果我使用调试生成(即调试生成单元测试程序集和发布生成目标程序集)专门配置单元测试项目,那么测试也会通过 要测试的代码C# 单元测试在调试生成中通过,但在发布生成中失败,c#,multithreading,unit-testing,C#,Multithreading,Unit Testing,我已经为一个异步类成员编写了一个单元测试。当我在“调试构建”下执行测试时,测试按预期通过。然而,当我在“发布构建”下执行测试时,它会挂起CPU(而循环处于死锁状态) 如果我使用调试生成(即调试生成单元测试程序集和发布生成目标程序集)专门配置单元测试项目,那么测试也会通过 要测试的代码 public override void DoSomething(object parameter) { ThreadPool.QueueUserWorkItem(AsyncDoSo
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#编译器在调试配置和发布配置下生成的IL:
在循环IL互穿时进行调试:
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
IL_00bc: ldloc.s 'CS$<>8__locals7'
IL_00be: ldfld bool IsSomethingDone
IL_00c3: brfalse.s IL_00bc
IL\u 00cb:ldloc.s'CS$8\u locals7'
IL_00cd:ldfld bool是一个很重要的角色
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
回路IL互穿时释放:
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
IL_00bc: ldloc.s 'CS$<>8__locals7'
IL_00be: ldfld bool IsSomethingDone
IL_00c3: brfalse.s IL_00bc
IL\u 00bc:ldloc.s'CS$8\u locals7'
这是一个很重要的问题
ILU 00c3:brfalse.s ILU 00bc
我知道有更好的方法将测试线程同步到后台线程池线程
我的问题是
在没有任何锁定(或其他内存障碍)的情况下,我相信
IsSomethingOne
需要声明为volatile
标志IsSomethingOne
被缓存在CPU寄存器中,并且从不从内存中重新加载,因此当一个线程修改它时,另一个线程永远看不到修改后的值。这是.NET JIT编译器所做的优化,因此您需要查看实际的x86/x64二进制文件dissasembly才能看到这一点,MSIL不够深入:-) 解决方案是将您的标志标记为
volatile
这是一个可能发生的多线程错误的教科书示例。如此之多,这里有一个链接到我在2012年新西兰TechEd上做的关于这个主题的演讲的一部分。希望它能更详细地解释发生了什么
正如您所提到的,您不能将volatile
放在局部变量上,但应该能够使用Thread.volatieread
我建议根本不要使用这种设计——在您的工作人员完成之前,您的主线程将使用100%的CPU进行旋转,这太可怕了。更好的解决方案是使用手动重置事件
(或其他类型的信号机制)
这里有一个例子
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.
}
谢谢你的重播,米奇。您不能声明局部变量volatile。@MitchWheat-您提到volatile将被弃用。真正地我没听说过-你有任何信息链接吗?我模糊地记得Eric Lippert提到过它…我必须搜索…嗯…找不到任何具体的东西…这是一个梦<代码>易失性被弃用没有多大意义。这在无锁代码中非常重要。是的,这正是我解决这个问题所做的。我还尝试了Thread.volatireRead,它也能解决这个问题。但方便的是,布尔类型没有重载的volatieread。是 啊我之所以发布这个问题,是为了理解为什么发布版本不起作用。我想MSIL确实不够深。