I want to exchange two nullable decimal values, like this:
o2 = Interlocked.Exchange(ref o1, o2);
The type 'decimal?' must be a reference type in order to use it as parameter 'T' in the generic type or method 'System.Threading.Interlocked.Exchange(ref T, T)'.
Is there better idea than this:
decimal? temp = o1;
o1 = o2;
o2 = temp;
Thanks in advance!
Two thoughts:
object
and cast at the consumer Box<T>
class where T:struct
(and make it immutable), and swap some Box<decimal>
references In both cases, the consumer should take a clone of the value before anything else (no double reads; it may change between reads).
Interlocked.Exchange
attempts to use the CPU's atomic instructions on whatever platform you are running on. These are atomic at the CPU level and require no locking. These instructions typically only work on platform words (usually 32 or 64 bits of memory).
Things that fit into a single word, like a int
, a byte
, or a reference to an object
on the heap can be manipulated atomically. Things that can't fit into a single word, such as a struct
like Nullable<decimal>
, or just a plain decimal
for that matter, can't be swapped atomically.
A workaround is to swap an object that references your decimal
(if it's non-null) or just a null value (if it is null). This object
is created for you automatically using a process known as boxing and unboxing.
public volatile object myNullableDecimal = null;
And then in your code you can do:
decimal? newValue = 34.3m;
decimal? oldvalue = (decimal?)Interlocked.Exchange(ref myNullableDecimal, newValue);
You will have to explicitly cast the values stored in myNullableDecimal
to decimal?
to use them because boxing is automatic, but unboxing is not.
Also, don't put an int
or anything but a Nullable<decimal>
or decimal
in myNullableDecimal because although a these types can be implicitly converted to a Nullable<decimal>
(via implicit conversion to a decimal
) a boxed T:struct
can only be converted to the underlying T
.
object myObj = 23; // will work but myObj is now a boxed int, not a boxed decimal
var myDecimal = (decimal?) myObj; // throws an exception! Can't convert boxed ints to Nullable<decimal>
Because of these tricky explicit casts, I recommend you wrap access to your 'object' using a method with the casts built in. This method will work for all nullable types, not just decimals. It throws a cast exception if the location doesn't actually contain the proper boxed type. Watch out though: it will still replace the old value before throwing the exception. That is to say that it is only atomic when it works as expected. If it fails, it might fail un-atomically.
public static T? ExchangeNullable<T>(ref object location, T? value) where T:struct
{
return (T?) Interlocked.Exchange(ref location, value);
}
A "safer" method that only replaces values that can be cast to the proper return type might look like the following. This method is non-blocking, atomic, and will never replace the old value if that value cannot be cast to the appropriate type. But this method is vulnerable to starvation since a thread might perpetually fail to update the value if it changes more frequently than the time it takes to verify the cast will succeed. To combat this, the method takes an optional CancellationToken
to allow it to be called with a timeout. The only way to avoid the starvation problem is by using locking (actually fair locking, which is even more expensive than regular locking).
This method is really only useful if you can't guarantee that the object won't get some other values places in it besides boxed types of the appropriate value type. If you are controlling all the access to the location in your own code this shouldn't be an issue, but since the compiler lets you call these methods on any object reference (which might point to just about anything), the update could fail and this method guarantees it fails atomically.
public static T? ExchangeNullableSafe<T>(ref object location, T? value, CancellationToken token = default(CancellationToken)) where T : struct
{
// get the expected value
var expected = location;
while (true)
{
// check if the cast works
if (expected is T?)
{
// cast works, try the update. This includes a memory barrier so we can just do a normal read to
// populate the expected value initially.
var actual = Interlocked.CompareExchange(ref location, value, expected);
// check if the update worked
if (actual == expected)
{
// update worked. Break out of the loop and return
break;
}
else
{
// cast worked but the value was changed before the update occurred.
// update the expected value to the one the CompareExchange op gave us and try again.
// again, the memory barrier in the CompareExchange method guarantees that we are updating the expected value each time we run through the loop
expected = actual;
}
}
else
{
// the cast will fail. Just break out of the loop, try the cast, and let it fail.
break;
}
// since this method is vulnerable to starvation, we allow for cancellation between loops.
token.ThrowIfCancellationRequested();
}
// return the value or throw an exception
return (T?)expected;
}
Now everything converts automatically, atomically, and without blocking
object myNullableDecimal = null;
// ...
decimal? oldValue;
oldValue = ExchangeNullable<decimal>(ref myNullableDecimal, m4 + 7); // works and is atomic
// oldValue is an empty Nullable<decimal>, myNullableDecimal is a boxed 13m
oldValue = ExchangeNullable<decimal>(ref myNullableDecimal, 7.4m); // also works
// oldValue is a Nullable<decimal> with value 13m, myNullableDecimal is a boxed 7.4m
var oldValue = ExchangeNullable<decimal>(ref myNullableDecimal, null); // yep, works too
// oldValue is a Nullable<decimal> with value 7.4m, myNullableDecimal is null
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.