2011-10-27 50 views
3

我们使用的是基本上没有这个的双显式铸造用不同的结果为int

var t = TimeSpan.MaxValue; 
int x = (int)t.TotalMilliseconds; 

其中x最终会被传递给System.Threading.WaitHandle.WaitOne(INT)的API。

问题是,当这些代码在我们的开发和分期环境中运行,它不抛出任何错误,但是当它在生产中运行它抛出:

Exception: System.ArgumentOutOfRangeException 
Message: Number must be either non-negative and less than or equal to Int32.MaxValue or -1. 
Parameter name: millisecondsTimeout 

当我测试这个用一个简单的控制台应用程序(x86和x64)x = -2147483648(int.MinValue)的结果,而当我在即时窗口中运行代码时,我得到x = 1566804069.

发生了什么事?

注:临时和生产都是从单个VM克隆所以有他们

之间没有差异THIS IS CODE,我们不能改变!否则我不会问这个问题。

+1

你转换溢出,那为什么你得到两种不同的结果,类型转换为长或Int64的 –

+0

@Tony没有,如果它是* *检查这将是一个溢出异常;所引用的例外表明*要么*表示持续时间正在包装,因为它未经检查*,或者间隔为负开始。 –

+1

@Dustin,这不会工作;最大时间间隔是10,675,199天;以毫秒为单位的int.MaxValue是25天。这不适合! - –

回答

4

这可以用相同的虚拟机发生的唯一方法是,如果CPU是从暂存计算机上的CPU生产机器上的不同 - 作为Gabe在评论的问题问及zdanhis answer建议。

对于发生了什么具体如此。对于支持SSE2的机器,.NET使用cvttsd2si指令将double转换为int,其中溢出映射为0x80000000(Int.MinValue)。在没有SSE2支持的机器上,我只能看到Rotor sources,而在jithelpers.cpp中,它只是将double转换为int32--它在VC10 C++上不带SSE2,最终返回低32位的值所以传递给等待的值应该是1566804069(0x5D638865),正如您在即时窗口中看到的那样。

CPU是不同的,你修改代码的“修复”是将机器改为不支持SSE2的机器。请参阅SSE2维基百科条目以检查生产服务器的CPU与临时服务器。如果你幸运的话,也许它可以在你的服务器的BIOS(或VM配置/ BIOS)中被禁用。

如果你敢,你可以尝试修补IL来解决这个问题 - 什么代码真正想要的是-1作为超时,这是“永远等待”。通过使用ilasm和ildasm,您可能可以修复它不带源代码(我假设这是您无法更改它的原因)。我这样做是成功地对自己的测试程序 - ildasm test.exe /out=test.il打开一个组装成IL,编辑的IL最后ilasm test.il /exe创建一个新的组件。下面是我的IL看起来像什么以及我如何修复它。

// bad code 
// var t = TimeSpan.MaxValue; 
IL_0008: call  instance float64System.TimeSpan::get_TotalMilliseconds() 

// int x = (int)t.TotalMilliseconds; 
IL_000D: conv.i4 // This is the line that becomes cvttsd2si when jitted 
IL_000E: stloc.2 

// wh.WaitOne(x); 
IL_000F: ldloc.0 
IL_0010: ldloc.2 
IL_0011: callvirt instance bool System.Threading.WaitHandle::WaitOne(int32) 

解决方法是调用wait一个

// fixed code 
// var t = TimeSpan.MaxValue; 
IL_0008: call  instance float64System.TimeSpan::get_TotalMilliseconds() 

// int x = (int)t.TotalMilliseconds; 
IL_000D: conv.i4 // This is the line that becomes cvttsd2si when jitted 
IL_000E: stloc.2 

// x = -1; // Fix by forcing x to -1 (infinite timeout) 
      ldc.i4.m1 // push a -1 
      stloc.2 // pop and store it in 'x' 

// wh.WaitOne(x); 
IL_000F: ldloc.0 
IL_0010: ldloc.2 
IL_0011: callvirt instance bool System.Threading.WaitHandle::WaitOne(int32) 

注意,在这种情况下, 'x' 为本地#2之前重新加载X(此处位置2)-1 - 在IL在该方法的顶部会给你正确的#所以需要改为无论#x被已经被分配在stloc.2 2,这应该只是前调用了WaitOne在标签IL_0010在匹配在ldloc指令#我例。

+0

我给你这个努力的答案。我结束了1)报告的错误,他们ar自我修复它,并让我知道什么时候一个新版本2)我使用PostSharp应用截取Milliseconds属性的一个方面,我将它改为适当的值(花式的方式做你的建议)。但是你提供了一个很好的解决方案。 –

0

您的转换溢出,为什么你得到不同的系统不同的结果。 使用长而不是

var t = TimeSpan.MaxValue; 
    long x = (long)t.TotalMilliseconds; 
+0

请REREAD的问题,这是在我们正在消费的API,所以我们不能改变它,否则我不会问这个问题。 –

+1

没有,*正确*要做的只是调用'了WaitOne(-1)',但OP不能这样做,因为他是唯一的环境中,而不是代码的控制。 – Gabe

0

TimeSpan.MaxValue是equivelant到Int64.MaxValue这是太大的传递给了WaitOne()值。如果你想传递一个大的值,只需使用Int32.MaxValue。

+0

这不是我无法更改的代码,您的回答无效。 –

+1

我不明白,你有代码崩溃,但你不能改变它?你期望如何解决这个问题? –

+0

我可以看到使用ILSpy的代码。 http://wiki.sharpdevelop.net/ILSpy。我不希望修复代码,我想知道为什么它可以在两个系统上工作,但不是另一个。 –

4

TimeSpan.MaxValue.TotalMilliseconds是等于922337203685477的double,它大于Int32.MaxValue(2147483647)。在这种情况下,演员会做的是特定实现(技术上它是undefined请参阅下面的@ phoog评论),并且可能取决于CPU,这可能会解释您所看到的差异。

在一种情况下,演员阵容会导致System.Threading.WaitHandle.WaitOne(int)可接受的值,而另一种情况则不然。

这似乎是您正在使用的库中的一个错误。有一个WaitOne超载需要TimeSpan作为参数,所以我不知道他们为什么不使用它。如果你不能改变图书馆,你是不走运的。

+0

是的,这是一个错误。非常刺激。 –

+0

你确定它是实现特定的吗?其他操作(附加,乘法等)的行为在溢出条件中已完全指定(选中vs未选中) - 我觉得缩小转换不会被限定。 –

+0

@Marc Gravell ECMA 335,分区III,3.27 conv。 - 数据转换“如果发生溢出将浮点类型转换为整数,或者如果将浮点值转换为整数是NaN,则返回的值未指定。 – phoog