Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 890
Sam Avatar asked Dec 05 '25 23:12

Sam


2 Answers

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.

like image 127
Niksr Avatar answered Dec 07 '25 11:12

Niksr


Here is an extension method GetOrAdd for the ConcurrentDictionary<TKey, TValue>, that has an additional out bool added parameter:

public static TValue GetOrAdd<TKey, TValue>(
    this ConcurrentDictionary<TKey, TValue> source,
    TKey key,
    Func<TKey, TValue> valueFactory,
    out bool added)
{
    ArgumentNullException.ThrowIfNull(source);
    ArgumentNullException.ThrowIfNull(valueFactory);
    if (source.TryGetValue(key, out TValue value)) { added = false; return value; }
    TValue newValue = valueFactory(key);
    while (true)
    {
        if (source.TryAdd(key, newValue)) { added = true; return newValue; }
        if (source.TryGetValue(key, out value)) { added = false; return value; }
    }
}

This implementation alternates between the TryGetValue and the TryAdd, until one of them succeeds. It is probably less efficient than Niksr's TryGetOrAdd. It has the advantage of supporting value types for the TValue, not only reference types.

A proposal for including this functionality in the standard .NET libraries has been submitted here (Dec 23, 2022). As of this writing, the proposal is still open.

A similar implementation has been posted in a Microsoft blog post here by Stephen Toub. The implementation in this answer has the advantage that it never invokes the valueFactory more than once.

like image 45
Theodor Zoulias Avatar answered Dec 07 '25 11:12

Theodor Zoulias



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!