4

Looking at the ConcurrentDictionary documentation it says the following:

Represents a thread-safe collection of key/value pairs that can be accessed by multiple threads concurrently.

Now when reading this it makes me think that I can call any method in the ConcurrentDictionary API and it will be thread safe...but it this meant to include explicit implementations aswell, do I have that guarantee?

My example is if I want an atomic operation to remove an item from a ConcurrentDictionary if its value is some value.

So I can do this:

var concurrentDictionary = new ConcurrentDictionary<string, string>();
concurrentDictionary.TryAdd("hey", "ho");

((ICollection<KeyValuePair<string, string>>) concurrentDictionary).Remove(new KeyValuePair<string, string>("hey", "ho"));

Now I have looked in the source code and that operation is both atomic and thread safe, but does the fact that its not on the ConcurrentDictionary API mean that I shouldn't use it...or maybe that I am using the collection to do something I shouldn't be doing with it.

I can take it one step further and write the following extension method:

public static boolean TryRemove(this ICollection<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
{
    return collection.Remove(new KeyValuePair<TKey, TValue>(key, value));
}

This will appear in Intellisense for ConcurrentDictionary because it implements the ICollection interface and many developers may not even know anything is untoward (if anything actually is?!)

EDIT: What I mean by "implicitly documented" is that ConcurrentDictionary implements a set of interfaces. It's documentation says that its thread safe but doesn't state if its only for the methods listed on that page but implies that all operations on an instance would be safe.

Cheetah
  • 13,785
  • 31
  • 106
  • 190
  • http://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs#1222 – SLaks Jun 08 '14 at 13:55
  • @SLaks, right... doh... my weekend reading skills are not very good. I'll remove my comment to avoid further confusion... –  Jun 08 '14 at 13:56
  • 1
    The explicit ICollection<>.Remove() method already calls TryRemove(), so your extension method is not useful. You only get a guarantee that ConcurrentDictionary is thread-safe, what you do with doesn't inherit that guarantee. – Hans Passant Jun 08 '14 at 15:16
  • @HansPassant - just an FYI, it makes a call to a private (internal) TryRemove method which does the operation atomically: `return TryRemoveInternal(keyValuePair.Key, out throwAwayValue, true, keyValuePair.Value);` – Cheetah Jun 08 '14 at 17:22

2 Answers2

0

This is a question about documented behavior. In general, you can only rely on documented behavior to actually hold. Other behavior can change at any time (at runtime, between runs of your app, between framework patch levels, ...).

If you can find a reference in the docs that this is safe then you are OK to do this.

If not, I'd be very careful in general. On the other hand, ConcurrentDictionary is a core type and the BCL team applies extreme compatibility standards to its work. I'd be OK with doing this even in production apps without this being documented. They are incredibly careful not to break callers even between major framework versions.

Because of those compatibility guarantees and this being a core type I'd be OK with deriving knowledge from the source code.

usr
  • 168,620
  • 35
  • 240
  • 369
0

You will not break the atomicity of ConcurrentDictionary by doing the things you describe.

You may cause unnecessary locks of all the buckets in concurrent dictionary. (by accessing it as a collection in certain ways)

This kills the "concurrent" part of the dictionary and would be less efficient than just surrounding a normal dictionary with locks.

The awesome part of ConcurrentDictionary is not that it is atomic- which could easily be done with locks, it is that it is concurrent, which means multiple threads can write to it atomicly with out having to wait for each other.

Once you get a better sense of this you will appreciate things like GetOrAdd, TryRemove, and TryUpdate because they allow an unambiguous transition to a state- even if you are unsure of the previous state.

Jason Hernandez
  • 2,907
  • 1
  • 19
  • 30
  • In reply to: `You will cause unnecessary locks of all the buckets in concurrent dictionary. (by accessing it as a collection)`...I'd have to disagree. Looking here: http://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs I can see it makes a call to `TryRemoveInternal()` which only locks the bucket its removed from. – Cheetah Aug 23 '14 at 07:45
  • You are correct. Remove calls tryremove. Other ways of accessing it may cause problems. Several of the ConcurrentDictionary calls require getting all the locks as well, but this is not well documented. – Jason Hernandez Aug 25 '14 at 16:00