Is there a way to identify whether a value was successfully found or if it was added when using ConcurrentDictionary.GetOrAdd?

117 views Asked by At

I have some code that uses the GetOrAdd function from ConcurrentDictionary to add values with a unique key to a holder. However, I want to be able to tell if a values key is found (i.e. a true returned from TryGet) or whether it has to be added.

I haven't been able to find anything online that suggests this is achievable so if anyone has any ideas it would be really helpful.

2

There are 2 answers

2
jmcilhinney On

Not consistently, no. If the method returns something other than the value that you provide then you know that the key already existed but if the key exists and has the same value that you provide then there will be no distinction. If you want to know definitively then you will have to test for the key first. It sounds like your best bet is to call ContainsKey first and then call GetOrAdd. You could wrap that into your own single method so it's easy to reuse and lock the dictionary if you need it to be atomic.

0
Niksr On

This is my lock-free attempt. GetOrAdd can invoke a delegate if at the moment of its calling there was no such key but ultimately can drop return if another thread won. The idea is to create a new object inside the lambda and then compare it with what GetOrAdd returns, in case if it was started as "Add", to reduce comparison overhead. It is not yet proven to be reliable but for me looks atomic and performant.

I made it as an extension:

public static class ConcurrentDictionaryExtensions
{
    public static bool TryGetOrAdd<TKey, TValue>(
        this ConcurrentDictionary<TKey, TValue> dictionary,
        TKey key,
        out TValue resultingValue,
        TValue existingValue = null) where TValue : class, new()
    {
        bool added = false;
        TValue newValue = null;

        resultingValue = dictionary.GetOrAdd(key, _ => {
            newValue = existingValue is null ? new TValue() : existingValue;
            added = true;
            return newValue;
        });

        return added && ReferenceEquals(resultingValue, newValue);
    }
}

Usage:

bool isNew = myDictionary.TryGetOrAdd(1, out var resultingValue, [existingValue]);

UPD: Added optional argument to pass existing object instead of creating new one. Extension seems to be proven to be reliable.