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

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

我的问题是

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

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

  3. 事件没有正确提出吗?

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

跟进问题:

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

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

Ori*_*rds 8

该标志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)