Using Interlocked.CompareExchange() operation on a bool value?
I have two questions:
Is there a need to use Interlocked class for acce开发者_高级运维ssing boolean values? Isn't a read or a write to a boolean value atomic by default?
I tried using Interlocked.CompareExchange on a boolean and got the following error:
bool value = true; Interlocked.CompareExchange<bool>(ref value, false, true);
Error: The type 'bool' must be a reference type in order to use it as parameter 'T' in the generic type or method 'System.Threading.Interlocked.CompareExchange(ref T, T, T)'
How do I go about solving this problem?
Reading or writing boolean values separately is atomic, but "compare and exchange" does both reading and writing to the same address, which means that entire transaction is not atomic. If multiple threads can write to this same location, you need to make the entire transaction atomic, by using the
Interlocked
class.public static T CompareExchange<T>(ref T a, T b, T c)) where T : class
overload can only be used with reference types (note thewhere T : class
clause at the end). Instead of a boolean value, you can use theCompareExchange(Int32, Int32, Int32)
overload, and switch the boolean with anInt32
.Alternatively, if you want to keep your variables of boolean type, you can use the
lock
method to ensure thread safety. This would be a slightly slower solution, but depending on your performance requirements, this might be still the preferred way.
You can use Interlocked.Exchange on an int
for this:
using System.Threading;
// ...
int boolValue = 0; // Start False
// ...
if (Interlocked.Exchange(ref boolValue, 1) == 1) // Set to True
{
// Was True
}
else
{
// Was False
}
Roll your own "AtomicBoolean" class (that wraps Interlocked.CompareExchange(...)
)
using System.Threading;
public class AtomicBoolean
{
private const int TRUE_VALUE = 1;
private const int FALSE_VALUE = 0;
private int zeroOrOne = FALSE_VALUE;
public AtomicBoolean()
: this(false)
{ }
public AtomicBoolean(bool initialValue)
{
this.Value = initialValue;
}
/// <summary>
/// Provides (non-thread-safe) access to the backing value
/// </summary>
public bool Value
{
get
{
return zeroOrOne == TRUE_VALUE;
}
set
{
zeroOrOne = (value ? TRUE_VALUE : FALSE_VALUE);
}
}
/// <summary>
/// Attempt changing the backing value from true to false.
/// </summary>
/// <returns>Whether the value was (atomically) changed from false to true.</returns>
public bool FalseToTrue()
{
return SetWhen(true, false);
}
/// <summary>
/// Attempt changing the backing value from false to true.
/// </summary>
/// <returns>Whether the value was (atomically) changed from true to false.</returns>
public bool TrueToFalse()
{
return SetWhen(false, true);
}
/// <summary>
/// Attempt changing from "whenValue" to "setToValue".
/// Fails if this.Value is not "whenValue".
/// </summary>
/// <param name="setToValue"></param>
/// <param name="whenValue"></param>
/// <returns></returns>
public bool SetWhen(bool setToValue, bool whenValue)
{
int comparand = whenValue ? TRUE_VALUE : FALSE_VALUE;
int result = Interlocked.CompareExchange(ref zeroOrOne, (setToValue ? TRUE_VALUE : FALSE_VALUE), comparand);
bool originalValue = result == TRUE_VALUE;
return originalValue == whenValue;
}
}
Example Usage:
class MultithreadedClass
{
private AtomicBoolean isUpdating = new AtomicBoolean(false);
public void Update()
{
if (!this.isUpdating.FalseToTrue())
{
return; //a different thread is already updating
}
try
{
//... do update.
}
finally
{
this.isUpdating.Value = false; //we are done updating
}
}
}
Test cases (if you're going to use it in production):
[TestClass]
public class AtomicBooleanTest
{
[TestMethod]
public void TestAtomicBoolean()
{
AtomicBoolean b = new AtomicBoolean();
Assert.IsFalse(b.Value);
b = new AtomicBoolean(false);
Assert.IsFalse(b.Value);
b = new AtomicBoolean(true);
Assert.IsTrue(b.Value);
//when Value is already true, FalseToTrue fails
b.Value = true;
Assert.IsFalse(b.FalseToTrue());
Assert.IsTrue(b.Value);
//when Value is already false, TrueToFalse fails
b.Value = false;
Assert.IsFalse(b.TrueToFalse());
Assert.IsFalse(b.Value);
//Value not changed if SetWhen fails
b.Value = false;
Assert.IsFalse(b.SetWhen(true, true));
Assert.IsFalse(b.Value);
//Value not changed if SetWhen fails
b.Value = true;
Assert.IsFalse(b.SetWhen(false, false));
Assert.IsTrue(b.Value);
}
}
You can't use interlocked for Boolean. You're better off using an int instead.
http://connect.microsoft.com/VisualStudio/feedback/details/98293/interlocked-compareexchange-should-offer-an-overload-for-boolean
精彩评论