具有零值双精度的奇怪 if 语句行为
Posted
技术标签:
【中文标题】具有零值双精度的奇怪 if 语句行为【英文标题】:Strange if-statement behavior with zero value double 【发布时间】:2014-04-06 13:56:02 【问题描述】:有人愿意向我解释this.oBalance.QouteBalance
的值是如何被评估为小于零的,而它显然不是吗?请看下图。
在比较 C# 中的双精度时,我是否遗漏了一些基本的东西?
public double QouteBalance get; set;
UpdateBalance_PositionOpenned()
不是在循环中被调用,而是作为更复杂的事件驱动过程的一部分被调用,该过程在计时器的滴答声(毫秒级)上运行
编辑:如果代码混乱,请原谅,但我无法编辑它,因为这是一个运行时错误,经过相当长的运行时间,所以担心无法重新创建它。异常消息不正确,只是对我自己的提醒。异常之后的代码是我在开始此特定运行之前忘记注释掉的代码。
编辑 2:我正在发布模式下构建和运行。
编辑 3: 请原谅我的无知,但看起来我实际上是在多线程环境中运行,因为此代码是作为执行的更复杂对象方法的一部分调用的在计时器的滴答声(事件)上。是否可以要求计时器等到其事件处理程序中的所有代码都完成后才能再次计时?
编辑 4: 因为这已被确定为多线程问题;我将尝试提供更广泛的背景以得出优化的解决方案。
我有一个Timer
对象,它在每个刻度上执行以下操作:
-
运行后台工作程序以从文件中读取数据
当后台工作人员完成从文件中读取数据时,引发
活动
在事件处理程序中,运行调用以下方法的目标代码
(在图片中)和其他多个例程,包括 GUI 更新。
我想这个问题可以通过使用计时器 Tick 事件来读取文件来避免,但是更改它会破坏我代码的其他部分。
【问题讨论】:
多线程? 如果越过当前行,真的会抛出异常吗?如果没有,您的符号可能已过时,请尝试重建。 注意:在抛出异常后尝试执行一行代码有点奇怪。以我目前的一周。我永远不会相信 Visual Studio 调试器会给出准确的值。尝试在某处输出它。 PS - 你没有在我看到的任何地方向我们展示QouteBalance
的定义。当然0
在这种情况下不是双重的......
@ArmenSafieh-Garabedian:如果你代表金钱,请使用decimal
而不是double
:***.com/questions/693372/…
鉴于您的最新更新,您对我们撒了谎!这是多线程的。事件在不同的线程上触发。这可能是一个简单的竞争条件,因为您正在从多个线程访问共享变量。
【参考方案1】:
您正在从多个线程访问共享变量。这可能是一种竞争条件,其中一个线程抛出了错误,但当调试器捕获并附加时,变量的值已经改变。
您需要考虑实现同步逻辑,例如锁定共享变量等。
编辑:回答您的编辑:
您不能真正告诉计时器不滴答(当然可以,但是您正在启动和停止,即使在调用 Stop 之后,您仍可能会收到更多事件,具体取决于它们被调度的速度)。也就是说,您可以查看 Interlocked 命名空间并使用它来设置和清除 IsBusy 标志。如果您的 tick 方法触发并看到您已经在工作,那么它就会停止这一轮并等待未来的 tick 来处理工作。我不会说这是一个很好的范例,但它是一种选择。
我指定使用 Interlocked 类而不是仅使用共享变量的原因归结为您可以同时从多个线程访问。如果您没有使用 Interlocked,您可能会得到两个滴答声,既检查值又得到答案,他们可以在翻转标志以阻止其他人之前继续进行。你会遇到同样的问题。
更传统的同步访问共享数据成员的方法是使用锁定,但您很快就会遇到滴答事件触发太快的问题,并且它们会开始支持您。
编辑 2:要回答有关在多个线程上将数据与共享变量同步的方法的问题,这实际上取决于您具体做什么。我们有一个很小的窗口可以了解您的应用程序正在做什么,因此我将从所有 cmets 和答案中将其拼凑起来,希望它能为您的设计选择提供参考。
下面是伪代码。这是基于您提出的一个问题,该问题表明您不需要对每个刻度都进行工作。滴答声本身并不重要,它只需要不断出现。基于此前提,我们可以使用标记系统来检查您是否忙。
...
Timer.Start(Handle_Tick)
...
public void Handle_Tick(...)
//Check to see if we're already busy. We don't need to "pump" the work if
//we're already processing.
if (IsBusy)
return;
try
IsBusy = true;
//Perform your work
finally
IsBusy = false;
在这种情况下,IsBusy 可能是一个 volatile bool,可以使用 Interlocked 命名空间方法访问它,它可能是一个锁定等。您可以选择什么。
如果这个前提是不正确的,而实际上你确实必须处理计时器的每一个滴答声,那么这对你来说就行不通了。你正在扔掉忙碌时进来的蜱虫。如果你想保持每一个进入的滴答声,你需要实现一个同步队列。如果你的频率很高,你必须小心,因为你最终会溢出。
【讨论】:
已编辑以响应您的最新编辑。您不能告诉计时器跳过滴答声,但您可以使用共享标志来指示您想在赶上时忽略一两个滴答声。这不是很好的代码,但它是一种选择。 如果我的滴答声比在事件处理程序中执行所有代码的时间慢,你会说我很可能没有这个问题吗? @ArmenSafieh-Garabedian 它必须“慢很多”。即便如此,由于大多数 GUI 进程(尤其是经过 GC 处理的进程)倾向于“打嗝”,因此即使是遥不可及的保证也算不了什么。 您不太可能击中它们,但它们仍然可能发生。事件并不总是在滴答声时立即触发。有许多事情可能会导致您的事件处理程序延迟执行。如果一个滴答声被延迟而另一个滴答声进入,您将遇到同样的问题。现在,如果执行您的代码需要几毫秒并且滴答声相隔 1 分钟,那么您可能永远不需要担心的可能性很低。但我需要强调的是,这仍然不好!这取决于您的数据完整性有多重要。 @ErikNoren 我想我会很想使用Monitor.TryEnter()
,等待超时非常短。如果我没有得到锁,我会在那个时候退出***别的事件(来自计时器)。 (此外,一旦我得到锁,我可以检查它自上次启动/停止时间以来已经足够长了,我应该做一些不会让系统陷入困境的事情)如果TryEnter(foo, 1)
太慢,那么我会考虑也许是我自己的控制线程而不是计时器......【参考方案2】:
这不是真正的答案,而是:
UpdateBalance_PositionOpenned() 不是在循环中调用,而是 被称为更复杂的事件驱动过程的一部分 在计时器的滴答声上运行(毫秒级)
见:
多线程? – abatishchev 30 分钟前
毫秒级的紧密计时器驱动的事件循环可能具有线程的所有问题,并且几乎完全不可能使用逐步调试器进行故障排除。事情发生的速度比你按“F10”要快得多。更不用说,您在每个事件周期都从不同的线程访问一个变量,但是看不到同步。
【讨论】:
宾果游戏!!!来自线程池的毫秒级计时器意味着很可能不止一个线程正在运行代码,并且所有数学都不受同步问题的保护。就是这样!!! @LB2 怪 abatishchev 他先得到它!调试跟踪(而不是逐步调试)在这里通常会有所帮助,特别是如果您根据需要使用同步来减慢速度...... @ArmenSafieh-Garabedian +1 祝你学习一些重要的东西!【参考方案3】:不是一个完整的答案,但评论太多了 这就是我可以进行防御性编码的方式 局部作用域可以减少意想不到的东西 它使代码更容易调试和测试
public void updateBalance(double amount, double fee, out double balance)
try
balance = amount * (1.0 + fee);
if (balance < 0.0) balance = 0.0;
catch (Exception Ex)
System.Diagnostics.Debug.WriteLine(Ex.Message);
throw Ex;
值类型被复制,因此即使在方法执行时更改了金额的输入变量,方法中的金额的值也不会发生变化。
现在没有锁的余额是另一回事。
【讨论】:
所以如果在这个方法完成执行之前发生滴答事件我仍然有问题吗? 这取决于你如何处理方法之外的平衡。但至少该方法保护了自己。如果您在多线程环境中,则在方法之外引用变量是有问题的。在方法调用中复制值时会产生开销。以上是关于具有零值双精度的奇怪 if 语句行为的主要内容,如果未能解决你的问题,请参考以下文章