C#代码优化导致Interlocked.Exchange()的问题

前端之家收集整理的这篇文章主要介绍了C#代码优化导致Interlocked.Exchange()的问题前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一个令人沮丧的问题,一些代码,不知道为什么会出现这个问题.
//
// .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从左边取值.最佳

因为解决方案#1与我的需求不符,我最终解决了#2:

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报告错误,让我知道,如果你不想花时间,我会照顾它.

猜你在找的C#相关文章