我想交换两个可为空的十进制值,如下所示:
o2 = Interlocked.Exchange(ref o1, o2);
类型'decimal?'必须是一个引用类型,才能将其用作泛型类型或方法'System.Threading.Interlocked '中的参数'T'。Exchange(ref T, T)'.
还有比这更好的主意吗?
decimal? temp = o1;
o1 = o2;
o2 = temp;
提前感谢!
两个想法:
- 将其视为
object
并在消费者处强制转换 - 创建
Box<T>
类where T:struct
(并使其不可变),并交换一些Box<decimal>
引用
在这两种情况下,消费者都应该在执行其他操作之前先获取该值的克隆(没有双读;
Interlocked.Exchange
尝试在运行的任何平台上使用CPU的原子指令。它们在CPU级别是原子的,不需要锁定。这些指令通常只适用于平台词(通常是32位或64位内存)。
适合单个单词的内容,如int
、byte
或对堆上object
的引用,可以自动操作。不能用单个单词表示的内容,例如struct
(如Nullable<decimal>
),或者仅仅是普通的decimal
,都不能自动交换。
一个解决方案是交换一个对象,引用你的decimal
(如果它是非空的)或只是一个空值(如果它是空的)。这个object
是通过一个称为装箱和拆箱的过程自动创建的。
public volatile object myNullableDecimal = null;
然后在你的代码中你可以这样写:
decimal? newValue = 34.3m;
decimal? oldvalue = (decimal?)Interlocked.Exchange(ref myNullableDecimal, newValue);
您必须显式地将存储在myNullableDecimal
中的值强制转换为decimal?
才能使用它们,因为装箱是自动的,但拆箱不是。
同样,不要在myNullableDecimal中放置int
或Nullable<decimal>
或decimal
以外的任何类型,因为尽管这些类型可以隐式转换为Nullable<decimal>
(通过隐式转换为decimal
),但是框化的 T:struct
只能转换为底层的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>
由于这些棘手的显式强制转换,我建议您使用内置强制转换的方法包装对"对象"的访问。此方法将适用于所有可空类型,而不仅仅是小数。如果位置实际上不包含正确的盒装类型,则会抛出强制转换异常。但是要注意:它仍然会在抛出异常之前替换旧值。也就是说,只有当它按预期工作时,它才是原子的。如果它失败了,它可能会自动失败。
public static T? ExchangeNullable<T>(ref object location, T? value) where T:struct
{
return (T?) Interlocked.Exchange(ref location, value);
}
一个"更安全"的方法,只替换可以转换为正确返回类型的值,可能如下所示。此方法是非阻塞的、原子的,并且如果旧值不能转换为适当的类型,则永远不会替换旧值。但是这个方法很容易受到饥饿的影响,因为如果一个线程更新值的频率超过了验证强制转换是否成功的时间,那么这个线程可能永远无法更新它的值。为了解决这个问题,该方法接受一个可选的CancellationToken
,以允许在超时时调用它。避免饥饿问题的唯一方法是使用锁定(实际上是公平锁定,它甚至比常规锁定更昂贵)。
这个方法只有在你不能保证对象中除了适当值类型的盒装类型之外不会有其他值的时候才有用。如果您在自己的代码中控制对位置的所有访问,这应该不是问题,但由于编译器允许您对任何对象引用(可能指向任何对象)调用这些方法,更新可能会失败,并且该方法保证它自动失败。
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;
}
现在所有的转换都是自动的,自动的,没有阻塞
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