Suzanne Soy
Suzanne Soy

Reputation: 3244

Read an up-to date value from an Interlocked variable, with only one write on the variable

I would like to create a class with two methods:

I have the following desires/constraints:

I came up with several ways to achieve that, but I'm not sure which are correct, which are efficient, why they are (in)correct and (in)efficient, and if there is a better way to achieve what I want.

Method 1

The code:

public class SetOnce1<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            // Read an up-to-date value of that variable
            Interlocked.CompareExchange<T>(ref _value, null, null);
            // _value contains up-to-date data, because of the Interlocked.CompareExchange call above.
            if (_value == null) {
                throw new System.Exception("Value not yet present.");
            }
        }

        // _value contains up-to-date data here too.
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        if (Interlocked.CompareExchange<T>(ref _value, newValue, null) != null) {
            throw new System.Exception("Value already present.");
        }

        return newValue;
    }
}

Method 2

The code:

public class SetOnce2<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        #pragma warning disable 0420
        T oldValue = Interlocked.CompareExchange<T>(ref _value, newValue, null);
        #pragma warning restore 0420

        if (oldValue != null) {
            throw new System.Exception("Value already present.");
        }
        return newValue;
    }
}

Method 3

The code:

public class SetOnce3<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            lock (this) {
                // Read an up-to-date value of that variable
                if (_value == null) {
                    throw new System.Exception("Value not yet present.");
                }
                return _value;
            }
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}

Method 4

The code:

public class SetOnce4<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}

Other methods

I could also use Thread.VolatileRead() to read the value, in combination with any of the writing techniques.

Upvotes: 6

Views: 1007

Answers (2)

Chris Sinclair
Chris Sinclair

Reputation: 23208

Well, not sure about volatility, but if you don't mind a little abuse and invoking a second method... (also it's not dependent on nullability; freely usable for value types) Avoids null checks in the getter too. Only locking is done on the write, so AFAIK, the only negative impact comes from invoking the delegate when getting the value.

public class SetOnce<T>
{
    private static readonly Func<T> NoValueSetError = () => { throw new Exception("Value not yet present.");};

    private Func<T> ValueGetter = NoValueSetError;
    private readonly object SetterLock = new object();

    public T SetValue(T newValue)
    {
        lock (SetterLock)
        {
            if (ValueGetter != NoValueSetError)
                throw new Exception("Value already present.");
            else
                ValueGetter = () => newValue;
        }

        return newValue;
    }

    public T GetValue()
    {
        return ValueGetter();
    }
}

In fact, I feel really silly about this and feels a little abusive. I'd be curious to see comments about potential issues of doing this. :)

EDIT: Just realized that this means the first call to SetValue(null) means that "null" will be considered a valid value and will return null without exception. Not sure if this is what you would want (I don't see why null couldn't be a valid value, but if you want to avoid it just do the check in the setter; no need in the getter)

EDITx2: If you still want to constrain it to class and avoid null values, a simple change might be:

public class SetOnce<T> where T : class
{
    private static readonly Func<T> NoValueSetError = () => { throw new Exception("Value not yet present.");};

    private Func<T> ValueGetter = NoValueSetError;
    private readonly object SetterLock = new object();

    public T SetValue(T newValue)
    {
        if (newValue == null)
            throw new ArgumentNullException("newValue");

        lock (SetterLock)
        {
            if (ValueGetter != NoValueSetError)
                throw new Exception("Value already present.");
            else
                ValueGetter = () => newValue;
        }

        return newValue;
    }

    public T GetValue()
    {
        return ValueGetter();
    }
}

Upvotes: 1

Andrey
Andrey

Reputation: 60065

None of your methods except the one with lock (#3) will work properly.

Look:

    if (_value == null) {
        throw new System.Exception("Value not yet present.");
    }
    return _value;

this code is not atomic and not thread-safe, if not inside lock. It is still possible that other thread sets _value to null between if and return. What you could do is to set to local variable:

    var localValue = _value;
    if (localValue == null) {
        throw new System.Exception("Value not yet present.");
    }
    return localValue;

But still it could return stall value. You would better use lock - clear, easy and fast.

Edit: Avoid using lock(this), because this is visible outside and third-party code may decide to lock on your object.

Edit 2: if null value can never be set then just do:

public T GetValue() {
    if (_value == null) {
        throw new System.Exception("Value not yet present.");
    }
    return _value;
}

public T SetValue(T newValue) {
    lock (writeLock)
    {        
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }
        _value = newValue;
        return newValue;
    }
}

Upvotes: 0

Related Questions