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
?
C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...
In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.
What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.
C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.
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 [comment mooted by JIT interception, see below])InterlockedExchange
functions to a private internal function that operates on TypedReference
[edit: July 2018] You can now do this more efficiently using the System.Runtime.CompilerServices.Unsafe library package. Your method can use Unsafe.As<TFrom,TTo>()
to directly reinterpret the type referenced by the target managed reference, avoiding the dual expenses of both pinning and transitioning to unsafe
mode:
static uint CompareExchange(ref uint target, uint value, uint expected) =>
(uint)Interlocked.CompareExchange(
ref Unsafe.As<uint, int>(ref target),
(int)value,
(int)expected);
static ulong CompareExchange(ref ulong target, ulong value, ulong expected) =>
(ulong)Interlocked.CompareExchange(
ref Unsafe.As<ulong, long>(ref target),
(long)value,
(long)expected);
Of course this works for Interlocked.Exchange
as well. Here are those helpers for the 4- and 8-byte unsigned types.
static uint Exchange(ref uint target, uint value) =>
(uint)Interlocked.Exchange(ref Unsafe.As<uint, int>(ref target), (int)value);
static ulong Exchange(ref ulong target, ulong value) =>
(ulong)Interlocked.Exchange(ref Unsafe.As<ulong, long>(ref target), (long)value);
This works for enumeration types also--but only so long as their underlying primitive integer is exactly four or eight bytes. In other words, int
(32-bit) or long
(64-bit) sized. The limitation is that these are the only two bit-widths found among the Interlocked.CompareExchange
overloads. By default, enum
uses int
when no underlying type is specified, so MyEnum
(from above) works fine.
static MyEnum CompareExchange(ref MyEnum target, MyEnum value, MyEnum expected) =>
(MyEnum)Interlocked.CompareExchange(
ref Unsafe.As<MyEnum, int>(ref target),
(int)value,
(int)expected);
static MyEnum Exchange(ref MyEnum target, MyEnum value) =>
(MyEnum)Interlocked.Exchange(ref Unsafe.As<MyEnum, int>(ref target), (int)value);
I'm not sure whether the 4-byte minimum is a fundamental to .NET, but as far as I can tell it leaves no means of atomically swapping (values of) the smaller 8- or 16-bit primitive types (byte
, sbyte
, char
, ushort
, short
) without risking collateral damage to adjacent byte(s). In the following example, BadEnum
explicitly specifies a size that is too small to be atomically swapped without possibly affecting up to three neighboring bytes.
enum BadEnum : byte { }; // can't swap less than 4 bytes on .NET?
If you're not constrained by interop-dictated (or otherwise fixed) layouts, a workaround would be to ensure that the memory layout of such enums is always padded to the 4-byte minimum to allow for atomic swapping (as int
). It seems likely, however, that doing so would defeat whatever purpose there might have been for specifying the smaller width in the first place.
[edit: April 2017] I recently learned that when .NET
is running in 32-bit mode (or, i.e. 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 those QWORD locations must occur through Interlocked
/Volatile
in order to preserve the guarantees, so you can't get cute assuming (e.g.) that direct (i.e., non-Interlocked
/Volatile
) reads are protected just because you always use Interlocked
/Volatile
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 codeAlthough 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With