简体   繁体   English

Interlocked.Exchange可为空的十进制

[英]Interlocked.Exchange nullable decimal

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)'. 为了将其用作通用类型或方法“ System.Threading.Interlocked.Exchange(ref T,T)”中的参数“ T”,必须为引用类型。

Is there better idea than this: 有没有比这更好的主意:

decimal? temp = o1;
o1 = o2;
o2 = temp;

Thanks in advance! 提前致谢!

Two thoughts: 两个想法:

  • treat it as object and cast at the consumer 将其视为object并投向消费者
  • create a Box<T> class where T:struct (and make it immutable), and swap some Box<decimal> references 创建一个Box<T> where T:struct (并使它不可变),并交换一些Box<decimal>引用

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. Interlocked.Exchange尝试在您正在运行的任何平台上使用CPU的原子指令。 These are atomic at the CPU level and require no locking. 这些在CPU级别是原子的,不需要锁定。 These instructions typically only work on platform words (usually 32 or 64 bits of memory). 这些指令通常仅适用于平台字(通常为32位或64位内存)。

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. 可以单个地操作适合单个单词的内容,例如intbyte或对堆上object的引用。 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. 无法将单个单词的内容(例如Nullable<decimal>类的struct或仅用于该内容的纯decimal不能原子交换。

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). 一种解决方法是交换引用decimal (如果非空)或仅引用空值(如果为空)的对象。 This object is created for you automatically using a process known as boxing and unboxing. 使用称为装箱和拆箱的过程自动为您创建该object

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? 您将必须将myNullableDecimal存储的值显式myNullableDecimaldecimal? 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 . 另外,请勿在myNullableDecimal中放入intNullable<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>

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. 为了解决这个问题,该方法采用了一个可选的CancellationToken以允许其超时调用。 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM