Bhargav Mangipudi
Bhargav Mangipudi

Reputation: 1233

Using Interlocked.CompareExchange() operation on a bool value?

I have two questions:

  1. Is there a need to use Interlocked class for accessing boolean values? Isn't a read or a write to a boolean value atomic by default?

  2. 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?

Upvotes: 38

Views: 27477

Answers (4)

Gian Marco
Gian Marco

Reputation: 23189

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                
}

Upvotes: 19

EthanB
EthanB

Reputation: 4289

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);
    }
}

Upvotes: 14

vgru
vgru

Reputation: 51214

  1. 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.

  2. public static T CompareExchange<T>(ref T a, T b, T c)) where T : class overload can only be used with reference types (note the where T : class clause at the end). Instead of a boolean value, you can use the CompareExchange(Int32, Int32, Int32) overload, and switch the boolean with an Int32.

    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.

Upvotes: 33

Shiroy
Shiroy

Reputation: 258

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

Upvotes: 10

Related Questions