【问题标题】:Code is behaving differently in Release vs Debug Mode代码在发布模式和调试模式下的行为不同
【发布时间】:2011-06-14 19:08:46
【问题描述】:

我们有一些单元测试在发布模式和调试模式下运行时会失败。如果我在发布模式下附加调试器,则测试通过。这里要发布的代码太多了,所以我真的只是在寻找调试发布模式问题的最佳实践。我已经检查了:

解决方案:在这种情况下,这是因为我正在比较浮点变量是否相等。如果不进行重大重构,我无法将浮点数更改为十进制,因此我添加了一个扩展方法:

public static class FloatExtension
{
    public static bool AlmostEquals(this float f1, float f2, float precision)
    {
        return (Math.Abs(f1 - f2) <= precision);
    }

    public static bool AlmostEquals(this float f1, float f2)
    {
        return AlmostEquals(f1, f2, .00001f);
    }

    public static bool AlmostEquals(this float? f1, float? f2)
    {
        if (f1.HasValue && f2.HasValue)
        {
            return AlmostEquals(f1.Value, f2.Value);
        }
        else if (f1 == null && f2 == null)
        {
            return true;
        }
        return false;
    }
}

【问题讨论】:

  • 几个问题。 1. 你在给这个问题一些“味道”时遇到了什么样的失败? 2.你检查过条件方法吗?
  • 主要问题是 Equals 方法返回 false。但是,如果我单独接受每个语句,它们都会返回 true。如果我尝试附加调试器,问题就会消失。
  • 是否与浮点相关(数据类型为双精度等)?
  • @Mark Byers 不,该应用是单线程的
  • @stefan 我正在比较 IsEqual 方法中的一些浮点值。

标签: c# .net release


【解决方案1】:

可能导致您看到的行为的一件事是导致race condition 的错误。附加调试器可以更改代码的时序,从而不再触发竞争条件。

要解决此问题,请在有多个线程访问数据时适当地使用同步。


我在 IsEqual 方法中比较一些浮点值。

这听起来是个非常糟糕的主意。你不应该比较浮点数是否相等,因为浮点计算不是 100% 精确的,你会得到表示和舍入错误。比较看看它们是否足够接近。对于涉及金钱的计算,您可能希望改用 decimal 类型。

【讨论】:

  • 感谢您的回答,但是应用程序的这个特定部分是单线程的。
  • 必须是浮点数。我将进行一些更改并进行验证。
【解决方案2】:

正如 Mark 所暗示的,这通常是与时间相关的问题的结果,通常是竞争条件或同步问题。

处理此类问题的一种常见方法是在受影响的区域使用“打印”语句来向您展示正在发生的事情。如果打印语句(Console.WriteLineResponse.Write、日志记录或其他)使问题消失,请将值存储在全局变量中,并在问题出现后打印全局变量。

最近一次发生在我身上的代码是从串行端口读取的。调试活动引起的时序变化足以影响来自串行端口的字节的缓冲方式,从而改变了缓冲区的解析方式。由于打印语句改变了时间,我不得不将数据存储起来以便稍后输出。

【讨论】:

    【解决方案3】:

    你应该问自己的问题 -

    1. 我的代码是线程化的吗?时间差异会影响输出
    2. 是否有人使用具有副作用的表达式调用 Debug.Assert()?
    3. 哪些对象实现了 IDisposable() 并以改变状态的方式这样做?
    4. 您是否正在 P/Invoking 非托管代码?

    在这种情况下,3 号很可能是个坏孩子。垃圾回收在 debug 和 release 中可能会有很大的不同,你可能会发现当一个对象被垃圾回收时会影响后面的单元测试的结果。

    仅供参考,如果您使用的是 NUnit 和 TestDriven.NET - 这两个测试以不同的顺序运行。

    【讨论】:

    • +1 用于提及垃圾收集方面的差异。在调试模式下,为了支持调试,对象往往会保持更长时间(例如,直到方法结束),而在发布模式下,对象通常会更早收集。
    【解决方案4】:

    由于它似乎与浮点相关,因此有很多事情可能出错。看: C# - Inconsistent math operation result on 32-bit and 64-bitDouble precision problems on .NET

    有很多东西可以用浮点数来处理。比较浮点数是否相等是一个普遍的禁忌。您应该检查小于合理 epsilon 的差异。

    【解决方案5】:

    这种情况经常发生,因为默认情况下调试版本未优化,即使您启用它,调试时的行为也会大不相同。您可以在“属性”->“构建”选项卡上的所有程序集的项目设置中禁用“优化代码”。

    当然还有其他可能导致差异的更改,例如您提到的条件方法就是其中之一。我发现这些很少是问题的原因,对我来说,它几乎总是优化器。

    优化器的经典陷阱包括“内联”的方法,因此它们无法出现在调用堆栈上。当使用 System.Diagnostics.StackFrame 类来确定当前执行点时,这会导致问题。同样,这将影响 MethodBase.GetCurrentMethod 或其他依赖于执行方法的函数/行为的结果。

    当然,我看到优化器做了很多我根本无法解释的事情。一个这样的例子在帖子'HashDerivedBytes - replacing Rfc2898DeriveBytes, but why?'中被记录和讨论,但我从未解开这个谜团。我只知道优化器在用于生成一系列派生字节时刚刚破坏了 Rfc2898DeriveBytes。奇怪的是,仅当生成的字节不能被所使用的哈希算法的大小 (20) 整除并且仅在前 20 个字节之后产生不正确的结果时才会中断。

    事实上,对代码产生不利影响的优化对于编译器来说并不是什么新鲜事。大多数老派 C++ 开发人员会立即告诉你,然后像我一样,讲述他们如何解决这个问题的冗长故事;)

    【讨论】:

    • 因此,如果在调试器下启动优化开启的构建,是否会抑制某些优化,可能导致原始问题中描述的行为?这方面的一个例子会很有帮助。
    【解决方案6】:

    只是为了增加我的两分钱,我最近发现我在测试调用的 sql 过程中进行了日期比较。这些日期都是在测试过程之前自动生成的,并且值被插入到数据库中,因此有时它们完全相同(使用 RunTests 时)导致在表连接上返回空值。不是我所期待的。显然,在调试模式下,由于我正在缓慢地进行,因此自动生成的时间会有所不同,这意味着我从未遇到过错误。我通过插入解决了这个问题

    Threading.Thread.Sleep(520)

    动作之间肯定会有延迟。问题已解决。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-25
      • 1970-01-01
      • 1970-01-01
      • 2014-06-24
      • 2017-06-16
      • 1970-01-01
      • 1970-01-01
      • 2012-05-19
      相关资源
      最近更新 更多