我想交换两个可以为空的十进制值,如下所示:
o2 = Interlocked.Exchange(ref o1, o2);
“十进制”类型?必须是引用类型才能在泛型类型或方法'System.Threading.Interlocked.Exchange(ref T,T)'中将其用作参数'T'。
有没有比这更好的想法:
decimal? temp = o1;
o1 = o2;
o2 = temp;
提前致谢!
答案 0 :(得分:5)
两个想法:
object
并投放给消费者Box<T>
类 where T:struct
(并使其不可变),并交换一些Box<decimal>
引用在这两种情况下,消费者应该在其他任何事情之前克隆该值(没有双读;它可能在读取之间发生变化)。
答案 1 :(得分:2)
Interlocked.Exchange
尝试在您运行的任何平台上使用CPU的原子指令。这些在CPU级别是原子的,不需要锁定。这些指令通常仅适用于平台字(通常为32或64位存储器)。
可以原子地操纵适合单个单词的内容,例如int
,byte
或对堆上object
的引用。不能容纳单个单词的内容,例如struct
,如Nullable<decimal>
,或只是普通的decimal
,不能原子地交换。
解决方法是交换引用您的decimal
的对象(如果它是非null)或仅交换空值(如果它为null)。使用称为装箱和拆箱的过程自动为您创建此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