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:
Interlocked.Exchange(ref float, float)
. 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:
int
instead, as Marc suggests. long
. 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.
上一篇: 无法找到入口点(cpp)
下一篇: C#联锁交换