C# 单元测试在调试生成中通过,但在发布生成中失败

C# 单元测试在调试生成中通过,但在发布生成中失败,c#,multithreading,unit-testing,C#,Multithreading,Unit Testing,我已经为一个异步类成员编写了一个单元测试。当我在“调试构建”下执行测试时,测试按预期通过。然而,当我在“发布构建”下执行测试时,它会挂起CPU(而循环处于死锁状态) 如果我使用调试生成(即调试生成单元测试程序集和发布生成目标程序集)专门配置单元测试项目,那么测试也会通过 要测试的代码 public override void DoSomething(object parameter) { ThreadPool.QueueUserWorkItem(AsyncDoSo

我已经为一个异步类成员编写了一个单元测试。当我在“调试构建”下执行测试时,测试按预期通过。然而,当我在“发布构建”下执行测试时,它会挂起CPU(而循环处于死锁状态)

如果我使用调试生成(即调试生成单元测试程序集和发布生成目标程序集)专门配置单元测试项目,那么测试也会通过

要测试的代码

    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

  • 是否因为eventhandler(lambda表达式)未执行

  • 事件是否未正确引发

  • 顺便说一下,我验证了DoSomething是否正确执行并生成了正确的结果

    后续问题:

  • 应该在调试构建或发布构建下构建单元测试项目吗

  • 应该在调试版本或发布版本下测试目标程序集吗


  • 在没有任何锁定(或其他内存障碍)的情况下,我相信
    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确实不够深。