Interlocked.Exchange 可空小数

发布于 2024-11-02 14:24:13 字数 315 浏览 4 评论 0原文

我想交换两个可为空的十进制值,如下所示:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

霊感 2024-11-09 14:24:13

两种想法:

  • 将其视为 object 并在消费者处
  • 创建一个 Box class where T:struct (并使其不可变),并交换一些 Box 引用

在这两种情况下,消费者都应该在执行其他操作之前获取该值的克隆(没有双重读取;它可能在读取之间发生变化) 。

Two thoughts:

  • treat it as object and cast at the consumer
  • create a 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).

为你鎻心 2024-11-09 14:24:13

Interlocked.Exchange 尝试在您运行的任何平台上使用 CPU 的原子指令。这些在 CPU 级别是原子的,不需要锁定。这些指令通常仅适用于平台字(通常是 32 或 64 位内存)。

适合单个单词的事物,例如 intbyte 或对堆上 object 的引用都可以进行原子操作。无法容纳在单个单词中的内容,例如像 Nullable 这样的 struct,或者只是一个简单的 decimal ,不能原子交换。

解决方法是交换引用您的十进制(如果它为非空)或仅引用空值(如果它为空)的对象。此对象是使用称为装箱和拆箱的过程自动为您创建的。

public volatile object myNullableDecimal = null;

然后在代码中您可以执行以下操作:

decimal? newValue = 34.3m;
decimal? oldvalue = (decimal?)Interlocked.Exchange(ref myNullableDecimal, newValue);

您必须将 myNullableDecimal 中存储的值显式转换为 decimal? 才能使用它们,因为装箱是自动的,但拆箱不是。

另外,不要在 myNullableDecimal 中放置 int 或除 Nullabledecimal 之外的任何内容,因为尽管这些类型可以隐式转换到 Nullable(通过隐式转换为 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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文