Interlocked.Exchange 可空小数
我想交换两个可为空的十进制值,如下所示:
o2 = Interlocked.Exchange(ref o1, o2);
“十进制”类型?必须是引用类型才能将其用作泛型类型或方法“System.Threading.Interlocked.Exchange(ref T, T)”中的参数“T”。
还有比这更好的主意吗:
decimal? temp = o1;
o1 = o2;
o2 = temp;
提前致谢!
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!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
两种想法:
object
并在消费者处Box
classwhere T:struct
(并使其不可变),并交换一些Box
引用在这两种情况下,消费者都应该在执行其他操作之前获取该值的克隆(没有双重读取;它可能在读取之间发生变化) 。
Two thoughts:
object
and cast at the consumerBox<T>
classwhere T:struct
(and make it immutable), and swap someBox<decimal>
referencesIn both cases, the consumer should take a clone of the value before anything else (no double reads; it may change between reads).
Interlocked.Exchange
尝试在您运行的任何平台上使用 CPU 的原子指令。这些在 CPU 级别是原子的,不需要锁定。这些指令通常仅适用于平台字(通常是 32 或 64 位内存)。适合单个单词的事物,例如
int
、byte
或对堆上object
的引用都可以进行原子操作。无法容纳在单个单词中的内容,例如像Nullable
这样的struct
,或者只是一个简单的decimal
,不能原子交换。解决方法是交换引用您的十进制(如果它为非空)或仅引用空值(如果它为空)的对象。此
对象
是使用称为装箱和拆箱的过程自动为您创建的。然后在代码中您可以执行以下操作:
您必须将
myNullableDecimal
中存储的值显式转换为decimal?
才能使用它们,因为装箱是自动的,但拆箱不是。另外,不要在 myNullableDecimal 中放置
int
或除Nullable
或decimal
之外的任何内容,因为尽管这些类型可以隐式转换到Nullable
(通过隐式转换为decimal
),装箱T:struct
可以仅被转换为底层T
。由于这些棘手的显式转换,我建议您使用内置转换的方法来包装对“对象”的访问。此方法适用于所有可为空的类型,而不仅仅是小数。如果该位置实际上不包含正确的装箱类型,则会抛出强制转换异常。但请注意:在抛出异常之前它仍然会替换旧值。也就是说,只有按预期工作时,它才是原子的。如果失败,它可能会以非原子方式失败。
仅替换可转换为正确返回类型的值的“更安全”方法可能如下所示。此方法是非阻塞的、原子的,并且如果旧值无法转换为适当的类型,则永远不会替换旧值。但此方法很容易出现饥饿问题,因为如果值的更改频率高于验证强制转换是否成功所需的时间,则线程可能永远无法更新该值。为了解决这个问题,该方法采用可选的 CancellationToken 来允许在超时时调用它。避免饥饿问题的唯一方法是使用锁定(实际上是公平锁定,这甚至比常规锁定更昂贵)。
仅当您无法保证对象不会在其中获取除适当值类型的装箱类型之外的其他值时,此方法实际上才有用。如果您在自己的代码中控制对该位置的所有访问,这应该不是问题,但由于编译器允许您在任何对象引用(可能指向任何对象)上调用这些方法),更新可能会失败,并且此方法保证它自动失败。
现在一切都会自动、原子地、无阻塞地转换
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
, abyte
, or a reference to anobject
on the heap can be manipulated atomically. Things that can't fit into a single word, such as astruct
likeNullable<decimal>
, or just a plaindecimal
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). Thisobject
is created for you automatically using a process known as boxing and unboxing.And then in your code you can do:
You will have to explicitly cast the values stored in
myNullableDecimal
todecimal?
to use them because boxing is automatic, but unboxing is not.Also, don't put an
int
or anything but aNullable<decimal>
ordecimal
in myNullableDecimal because although a these types can be implicitly converted to aNullable<decimal>
(via implicit conversion to adecimal
) a boxedT:struct
can only be converted to the underlyingT
.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.
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.
Now everything converts automatically, atomically, and without blocking