5

The scenario I have is I want a method on ConcurrentDictionary like this.

bool TryRemove(TKey key, TValue value) {
    // remove the value IF the value passed in == dictionary[key]
    // return false if the key is not in the dictionary, or the value is not equal
}

Is there a way to do this concurrently? I'm struggling to find an answer for this scenario, even though it seems like this is a common use case.

I could do something like this, but I want to avoid a lock if I'm already using a ConcurrentDictionary. I'd also have to have locks on GetOrAdd() or AddOrUpdate() calls elsewhere. It just seems like there should be a better way with a ConcurrentDictionary.

ConcurrentDictionary<int, string> dict = ...;

/// stuff

int keyTryToRemove = 1337;
string valTryToRemove = "someValue";

bool success = false;
lock(keyTryToRemove) {
    string val;
    if (dict.TryRemove(keyTryToRemove, out val)) {
        if (val == valTryToRemove) {
            success = true;
        }
        else { // reinsert value, UGLY!
            dict[keyTryToRemove] = val;
            success = false;
        }
    } else {
        success = false;
    }
}
Frank Bryce
  • 8,076
  • 4
  • 38
  • 56
  • 3
    It already exists: [ConcurrentDictionary.TryRemove](https://msdn.microsoft.com/en-us/library/dd287129(v=vs.110).aspx) – Igor Sep 15 '16 at 16:58
  • What about doing a `TryGetValue` and the a `Remove` if it returns true and the value matches? You'd still need the lock though. – juharr Sep 15 '16 at 16:59
  • @Igor That removes based on the key and returns the value, the OP want to remove the entry if the key and value both match. They even use it in there attempt. – juharr Sep 15 '16 at 17:00
  • @Igor that's exactly what I don't want. I want to conditionally remove the value, only if it matches the value I passed to it. – Frank Bryce Sep 15 '16 at 17:01
  • @juharr - Ah, I missed that. – Igor Sep 15 '16 at 17:01
  • The best option is to write an extension method and to implement your own lock on the Dictionary instance passed in. Inside the lock there is no need to call any of the existing methods that also lock. – Igor Sep 15 '16 at 17:02
  • @Igor if I do this, then I'd need access to the same lock inside the extension method that I need to use on the `GetOrAdd()` and `AddOrUpdate()` calls, right? – Frank Bryce Sep 15 '16 at 17:08
  • @JohnCarpenter - not sure why the down vote, its was a good question (IMO). +1 from me. – Igor Sep 15 '16 at 17:13

3 Answers3

7

Since ConcurrentDictionary<TKey, TValue> class implements (although explicitly) IDictionary<TKey, TValue>, thus ICollection<KeyValuePair<TKey, TValue>>, you can simply cast it to the later and use Remove method like this:

bool success = ((ICollection<KeyValuePair<TKey, TValue>>)dict).Remove(
    new KeyValuePair<TKey, TValue>(key, value));

The implementation internally uses the same thread safe method (passing additionally the value to be checked) as the public TryRemove method - exactly as it should be.

Edit: Generally speaking, the method in question can be made available for any type implementing IDictionary<TKey, TValue> (or more precisely ICollection<KeyValuePair<TKey, TValue>>) like Dictionary, ConcurrentDictionary etc. by introducing a custom extension method like this:

public static class Extensions
{
    public static bool TryRemove<TKey, TValue>(this ICollection<KeyValuePair<TKey, TValue>> source, TKey key, TValue value)
    {
        return source.Remove(new KeyValuePair<TKey, TValue>(key, value));
    }
}

so the sample code becomes simply:

bool success = dict.TryRemove(key, value);
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • woah, that's cool, and I think exactly what I'm looking for. One question, this is still thread safe, right? – Frank Bryce Sep 15 '16 at 17:11
  • 2
    @JohnCarpenter - it is, I just checked. – Igor Sep 15 '16 at 17:12
  • 1
    It uses the same internal implementation as the public `TryRemove` - [source](http://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs,4d3f2a076a068c2e) – Ivan Stoev Sep 15 '16 at 17:15
-1

I'd do something like this

bool success = false;
lock(lockForDictionary) 
{
    string val;
    if (dict.TryGetValue(keyTryToRemove, out val) && val == valTryToRemove) 
    {
        dict.Remove(keyTryToRemove);
        success = true;
    } 
}
juharr
  • 31,741
  • 4
  • 58
  • 93
  • What is `lockForDictionary`? – ebyrob Sep 15 '16 at 17:06
  • Yeah, this is much better than what I did in my answer. Would it be possible only to lock on the key? I think yes, but I wanted to verify that's the case – Frank Bryce Sep 15 '16 at 17:06
  • @JohnCarpenter No, you want your lock to be for the specific dictionary instance. I'm leaving that for you to determine how to implement, but I would also not make use of the dictionary itself as the lock, but just a `object` specifically created just for locking on. – juharr Sep 15 '16 at 17:08
  • @JohnCarpenter How are you going to guarantee the key is thread safe? You never want to use an object that is owned by an external entity as a lock – Tibrogargan Sep 15 '16 at 17:09
-1

Below is a piece of code that simplify the steps a bit.

readonly object _locker = new object();
readonly ConcurrentDictionary<int, string> _dict = new ConcurrentDictionary<int, string>();

public bool TryRemove(int key, string value)
{
    var success = false;
    lock (_locker)
    {
        if (_dict.ContainsKey(key) && _dict[key] == value)
        {
            string val;
            success = _dict.TryRemove(key, out val);
        }
    }
    return success;
}

With that said, it seems the goal is non atomic in nature and this is why we have the need for a lock. It's important to ask, what is your goal and can you express the goal in an atomic way. 2 useful methods of ConcurrentDictionary include TryUpdate and AddOrUpdate. Would any of those methods help?

Paul Tsai
  • 893
  • 6
  • 16
  • You answered after someone already posted an atomic solution, though I appreciate the effort you put into your answer. – Frank Bryce Sep 16 '16 at 15:29
  • You're welcome! Each answer on here is slightly different. These differences can have a big performance difference in multi-threaded code. In the code above, the value in the dictionary is checked without removal. This is big since adding and removing is an expensive operation. Only when the conditions for removal is met is the value removed. – Paul Tsai Sep 17 '16 at 00:13