C# Interlocked Exchange

I have a bit of my game which looks like this:

public static float Time;

float someValue = 123;
Interlocked.Exchange(ref Time, someValue);

I want to change Time to be a Uint32; however, when I try to use UInt32 instead of float for the values, it protests that the type must be a reference type. Float is not a reference type, so I know it's technically possible to do this with non-reference types. Is there any practical way to make this work with UInt32 ?


Although ugly, it is actually possible to perform an atomic Exchange or CompareExchange on an enum or other blittable value type of 64 bits or less using unsafe C# code:

enum MyEnum { A, B, C };

MyEnum m_e = MyEnum.B;

unsafe void example()
{
    MyEnum e = m_e;
    fixed (MyEnum* ps = &m_e)
        if (Interlocked.CompareExchange(ref *(int*)ps, (int)(e | MyEnum.C), (int)e) == (int)e)
        {
            /// change accepted, m_e == B | C
        }
        else
        {
            /// change rejected
        }
}

The counterintuitive part is that the ref expression on the dereferenced pointer does actually penetrate through to the address of the enum. I think the compiler would have been within its rights to have generated an invisible temporary variable on the stack instead, in which case this wouldn't work. Use at your own risk.

[edit: for the specific type requested by the OP]

static unsafe uint CompareExchange(ref uint target, uint v, uint cmp)
{
    fixed (uint* p = &target)
        return (uint)Interlocked.CompareExchange(ref *(int*)p, (int)v, (int)cmp);
}

[edit: and 64-bit unsigned long]

static unsafe ulong CompareExchange(ref ulong target, ulong v, ulong cmp)
{
    fixed (ulong* p = &target)
        return (ulong)Interlocked.CompareExchange(ref *(long*)p, (long)v, (long)cmp);
}

(I also tried using the undocumented C# keyword __makeref to achieve this, but this doesn't work because you can't use ref on a dreferenced __refvalue . It's too bad, because the CLR maps the InterlockedExchange functions to a private internal function that operates on TypedReference [comment mooted by JIT interception, see below])


[edit: April 2017] I recently learned that when .NET is running in 32-bit mode (or, ie in the WOW subsystem), the 64-bit Interlocked operations are not guaranteed to be atomic with respect to non- Interlocked , "external" views of the same memory locations. In 32-bit mode, the atomic guarantee only applies globablly across QWORD accesses which use the Interlocked (and perhaps Volatile.* , or Thread.Volatile* , TBD?) functions.

In other words, to obtain 64-bit atomic operations in 32-bit mode, all accesses to QWORD locations must occur through Interlocked in order to preserve the guarantees, and you can't get cute assuming that (eg) direct reads are protected just because you always use Interlocked functions for writing.

Finally, note that the Interlocked functions in the CLR are specially recognized by, and receive special treatment in, the .NET JIT compiler. See here and here This fact may help explain the counter-intuitiveness I mentioned earlier.


There's an overload for Interlocked.Exchange specifically for float (and others for double , int , long , IntPtr and object ). There isn't one for uint, so the compiler reckons the closest match is the generic Interlocked.Exchange<T> - but in that case T has to be a reference type. uint isn't a reference type, so that doesn't work either - hence the error message.

In other words:

  • Your current code works because it calls Interlocked.Exchange(ref float, float) .
  • Changing it to uint fails because there's no applicable overload. The exact error message is caused by the compiler guessing that you mean Interlocked.Exchange<T>(ref T, T) .
  • As for what to do, the options are any of:

  • Potentially use int instead, as Marc suggests.
  • If you need the extra range, think about using long .
  • Use uint but don't try to write lock-free code
  • Although obviously Exchange works fine with some specific value types, Microsoft hasn't implemented it for all the primitive types. I can't imagine it would have been hard to do so (they're just bits, after all) but presumably they wanted to keep the overload count down.


    Perhaps use int instead of uint ; there are overloads for int . Do you need the extra bit of range? If so, cast / convert as late as possible.

    链接地址: http://www.djcxy.com/p/44472.html

    上一篇: 无法找到入口点(cpp)

    下一篇: C#联锁交换