我有一个令人沮丧的问题,一些代码,不知道为什么会出现这个问题.
// // .NET FRAMEWORK v4.6.2 Console App static void Main( string[] args ) { var list = new List<string>{ "aa","bbb","cccccc","dddddddd","eeeeeeeeeeeeeeee","fffff","gg" }; foreach( var item in list ) { Progress( item ); } } private static int _cursorLeft = -1; private static int _cursorTop = -1; public static void Progress( string value = null ) { lock( Console.Out ) { if( !string.IsNullOrEmpty( value ) ) { Console.Write( value ); var left = Console.CursorLeft; var top = Console.CursorTop; Interlocked.Exchange( ref _cursorLeft,Console.CursorLeft ); Interlocked.Exchange( ref _cursorTop,Console.CursorTop ); Console.WriteLine(); Console.WriteLine( "Left: {0} _ {1}",_cursorLeft,left ); Console.WriteLine( "Top: {0} _ {1}",_cursorTop,top ); } } }
当没有代码优化运行时,结果是按预期的. _cursorLeft和left-_cursorTop和top相等.
aa Left: 2 _ 2 Top: 0 _ 0 bbb Left: 3 _ 3 Top: 3 _ 3
但是当我运行代码优化时,这两个值_cursorLeft和_cursorTop变成bizzare:
aa Left: -65534 _ 2 Top: -65536 _ 0 bb Left: -65533 _ 3 Top: -65533 _ 3
我发现了两个解决方法:
>将_cursorLeft和_cursorTop设置为0而不是-1
让Interlocked.Exchange从左边取值.最佳
private static int _cursorLeft = -1; private static int _cursorTop = -1; public static void Progress( string value = null ) { lock( Console.Out ) { if( !string.IsNullOrEmpty( value ) ) { Console.Write( value ); // OLD - does NOT work! //Interlocked.Exchange( ref _cursorLeft,Console.CursorLeft ); //Interlocked.Exchange( ref _cursorTop,Console.CursorTop ); // NEW - works great! var left = Console.CursorLeft; var top = Console.CursorTop; Interlocked.Exchange( ref _cursorLeft,left ); // new Interlocked.Exchange( ref _cursorTop,top ); // new } } }
但这个奇怪的行为是从哪里来的?
有没有更好的解决方法/解决方案?
[由Matthew Watson编辑:添加简化版本:]
class Program { static void Main() { int actual = -1; Interlocked.Exchange(ref actual,Test.AlwaysReturnsZero); Console.WriteLine("Actual value: {0},Expected 0",actual); } } static class Test { static short zero; public static int AlwaysReturnsZero => zero; }
[由我编辑:]
我想出了另一个更短的例子:
class Program { private static int _intToExchange = -1; private static short _innerShort = 2; // [MethodImpl(MethodImplOptions.NoOptimization)] static void Main( string[] args ) { var oldValue = Interlocked.Exchange(ref _intToExchange,_innerShort); Console.WriteLine( "It was: {0}",oldValue ); Console.WriteLine( "It is: {0}",_intToExchange ); Console.WriteLine( "Expected: {0}",_innerShort ); } }
除非您不使用Optimization或将_intToExchange设置为在ushort范围内的值,否则您将无法识别该问题.
解决方法
您正确地诊断了问题,这是一个优化器错误.它特定于64位抖动(又名RyuJIT),这是在VS2015中首次开始发货的.您只能通过查看生成的机器代码来查看它.在我的机器上看起来像这样:
00000135 movsx rcx,word ptr [rbp-7Ch] ; Cursor.Left 0000013a mov r8,7FF9B92D4754h ; ref _cursorLeft 00000144 xchg cx,word ptr [r8] ; Interlocked.Exchange
XCHG指令错误,它使用16位操作数(cx和word ptr).但变量类型需要32位操作数.因此,变量的高16位保持为0xffff,使整个值为负.
表征这个错误有点棘手,这是不容易孤立.获取Cursor.Left属性getter inlined似乎有助于触发错误,在引擎盖下它访问一个16位字段.显然,以某种方式,让优化者决定一个16位的交换将完成这项工作.为什么您的解决方法代码解决了这个原因,使用32位变量来存储Cursor.Left / Top属性会使优化器成为一个好的编程路径.
在这种情况下,解决方法是一个非常简单的一个,超出了你发现的,你根本不需要Interlock,因为lock语句已经使代码线程安全.请在connect.microsoft.com报告错误,让我知道,如果你不想花时间,我会照顾它.