单元测试在调试版本中传递,但在版本构建中失败

时间:2013-12-10 01:39:13

标签: 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();
    }

我的单元测试

    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线程同步。

我的问题是

  1. 为什么发布版本不起作用?工作线程未设置标志IsSomethingDone。

  2. 是因为eventhandler(lambda表达式)没有被执行?

  3. 事件是否未正确引发?

  4. 顺便说一句,我确认DoSomething正确执行并生成了正确的结果。

    跟进问题:

    1. 是否应在Debug Build或Release Build下构建单元测试项目?

    2. 是否应在Debug Build或Release Build下测试目标程序集?

2 个答案:

答案 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