简体   繁体   English

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

[英]Unit test passes in debug build but fails in release build

I have written a unit test for an asynchronous class member. 我为异步类成员编写了一个单元测试。 The test passes as expected when I execute my test under "Debug build". 当我在“Debug build”下执行测试时,测试按预期传递。 However, it hangs the CPU (while loop is deadlocked) when I execute my test under "Release build". 但是,当我在“Release build”下执行我的测试时,它会挂起CPU(当循环死锁时)。

If I specifically configure the Unit test project with Debug build (that is Debug build unit test assembly and Release build target assembly), the test passes as well. 如果我使用Debug构建(即Debug构建单元测试程序集和Release构建目标程序集)专门配置Unit测试项目,那么测试也会通过。

Code to Test 要测试的代码

    public override void DoSomething(object parameter)
    {
        ThreadPool.QueueUserWorkItem(AsyncDoSomething, parameter);
    }

    private void AsyncDoSomething(object parameter)
    {
        //Doing something
                .....

        //Something is done
        RaiseSomethingIsDone();
    }

My Unit Test 我的单元测试

    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.
    }

Here is the IL generated by the C# compiler under Debug configuration and Release configuration: 这是在调试配置和发布配置下由C#编译器生成的IL:

Debug while loop IL interpenetration: 调试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

Release while loop IL interpenetration: 释放while循环IL互穿:

  IL_00bc:  ldloc.s    'CS$<>8__locals7'
  IL_00be:  ldfld      bool IsSomethingDone
  IL_00c3:  brfalse.s  IL_00bc

I understand there are better ways to synchronize the test thread to the background ThreadPool thread. 我知道有更好的方法可以将测试线程与后台ThreadPool线程同步。

My questions are 我的问题是

  1. why is the release build not working? 为什么发布版本不起作用? The flag IsSomethingDone is not set by the worker thread. 工作线程未设置标志IsSomethingDone。

  2. Is it because the eventhandler (the lambda expression) does not get executed? 是因为eventhandler(lambda表达式)没有被执行?

  3. Is the event not raised correctly? 事件没有正确提出吗?

By the way, I verified that DoSomething is executed correctly and generated the correct result. 顺便说一句,我确认DoSomething正确执行并生成了正确的结果。

Follow up Questions: 跟进问题:

  1. Should one build unit test projects under Debug Build or Release Build ? 是否应该在Debug Build或Release Build下构建单元测试项目?

  2. Should one test the target assembly under Debug Build or Release Build ? 应该在Debug Build或Release Build下测试目标程序集吗?

The flag IsSomethingDone is being cached in a CPU register, and never reloaded from memory, so that when one thread modifies it, the other never sees the modified value. 标志IsSomethingDone正在高速缓存在CPU寄存器中,并且永远不会从内存重新加载,因此当一个线程修改它时,另一个线程永远不会看到修改后的值。
This is an optimization which the .NET JIT compiler does, so you'd need to look at the actual x86/x64 binary dissasembly to see this, MSIL is not deep enough :-) 这是.NET JIT编译器所做的一个优化,所以你需要查看实际的x86 / x64二进制dissasembly才能看到这个,MSIL不够深入:-)

The solution is to mark your flag as volatile 解决方案是将您的标记标记为volatile

This is a textbook example of one of the kind of multithreading bugs that can occur. 这是可能发生的一种多线程错误的教科书示例。 So much so, that here's a link to part of a presentation I did at TechEd New Zealand 2012 on this exact subject. 这么多,这是我在TechEd New Zealand 2012上就这个确切主题所做的演示的一部分的链接。 Hopefully it should explain what's going on in more detail 希望它能够更详细地解释发生了什么

http://orionedwards.blogspot.co.nz/2012/09/teched-2012-background-2-compiler.html http://orionedwards.blogspot.co.nz/2012/09/teched-2012-background-2-compiler.html

As you have mentioned, you can't put volatile on a local variable, but you should be able to use Thread.VolatileRead instead. 正如您所提到的,您不能将volatile放在局部变量上,但您应该能够使用Thread.VolatileRead

I'd suggest not using this design at all - your main thread is going to spin using 100% CPU until your worker completes, which is horrible. 我建议根本不使用这个设计 - 你的主线程将使用100%CPU旋转,直到你的工人完成,这太可怕了。 A better solution is to use a ManualResetEvent (or other kind of signaling mechanism). 更好的解决方案是使用ManualResetEvent (或其他类型的信令机制)。

Here's an example 这是一个例子

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.
}

在没有任何锁定(或其他内存屏障)的情况下,我认为IsSomethingDone需要声明为volatile

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM