How to increment (add value to) decimal in a thread-safe way?
No. The internal representation of decimal
is too complex for modifications to be made with atomic instructions at the CPU level (which is what Interlocked
does most of the time, and which is what you are interested in).
When the CPU cannot atomically handle some quantity manual locking is the only option. You get to choose the synchronization primitive (e.g. lock
vs a mutex) but that's it.
You can still use InterLocked
, but then you have to convert the decimal to an Int64
. With the conversion you have to decide how many decimal places you want to preserve for precision. So for example, you want to preserve 4 decimal places, you could do something like this:
//Declare up front accessible from all threads
Int64 totalAmount = 0;
//Inside the thread you do this
var amount = (Int64)(decimalAmount * 10000); //10.000 is to preserve 4 decimal places
Interlocked.Add(ref totalAmount, amount);
//After all threads have finished, go back to decimal type.
var totalDecimalAmount = totalAmount / 10000;
Be aware that you will lose precision, depending on how many decimal places you would like to preserve. And Decimal.MaxValue
is 79,228,162,514,264,337,593,543,950,335
whereas Int64.MaxValue
is 9,223,372,036,854,775,807
. So very large numbers won't fit. Preserving 4 decimal places, the largest number before the Int64 would overflow is 9,223,372,036,854,775,807 / 10000 = 922,337,203,685,477
I use it this way as numbers here will never go above 1,000,000,000 and I am sure that using Interlocked
this way is faster in a Parallel.For loop then using a lock
or mutex.
Using lock is not overkill. It is required.
Structure types like System.Decimal are never atomic, it also doesn't fit the native cpu word size. Which is why Interlocked doesn't have an overload for it either.