Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to use ConcurrentDictionary.TryUpdate with a lambda expression?

I have a simple scenario where I want to update the value of an existing item. Only AddOrUpdate method offers a delegate where I can update the old value. However I don't want to add anything if the key does not exist. Also TryUpdate method has no overloads that I can get the old value. Is there a way to do it with current API?

Here's the signature I am looking for:

bool TryUpdate(TKey key, Func<TValue,TValue> updateValueFactory)
like image 279
Ufuk Hacıoğulları Avatar asked Sep 02 '12 22:09

Ufuk Hacıoğulları


1 Answers

You have to be prepared to loop and perhaps call the Func more than once (the same as with the overload of GetOrAdd that uses one). This means that if the Func has side-effects, it will not appear to be atomic from the outside. Really, Funcs shouldn't have side-effects, but they always have some cost so the possibility of repeat calls can't be ignored:

public static bool TryUpdate<TKey, TValue>(
  this ConcurrentDictionary<TKey, TValue> dict,
  TKey key,
  Func<TValue, TValue> updateFactory)
{
    TValue curValue;
    while(dict.TryGetValue(key, out curValue))
    {
        if(dict.TryUpdate(key, updateFactory(curValue), curValue))
            return true;
        // if we're looping either the key was removed by another thread,
        // or another thread changed the value, so we start again.
    }
    return false;
}

As said, because it can loop, it's only atomic as observed from the outside if there are no side-effects in the Func.

(Edit: Deleting a potential short-cut that is really too fraught to generally be used, and likely to bite someone who tried it).

like image 129
Jon Hanna Avatar answered Sep 18 '22 14:09

Jon Hanna