tags:

views:

843

answers:

3

I'm perusing (considering writing my own thread safe dictionary) I found the following implementation.

http://devplanet.com/blogs/brianr/archive/2008/09/26/thread-safe-dictionary-in-net.aspx

It looks pretty good generally, but there's one thing that confuses me.

The following: Cannot enumerate a threadsafe dictionary. Instead, enumerate the keys or values collection

which is found in both the

public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
    throw new NotSupportedException("Cannot enumerate a threadsafe dictionary.  Instead, enumerate the keys or values collection");
}


IEnumerator IEnumerable.GetEnumerator()
{
    throw new NotSupportedException("Cannot enumerate a threadsafe dictionary.  Instead, enumerate the keys or values collection");
}

What I don't get is why it's ok to enumerate the keys or values, but not the kvp's of the dictionary?

Could someone shed some light on this for me? Thanks in advance.

+3  A: 

SO user and MS employee JaredPar has a short series on his blog about building threadsafe collections that's definitely worth your time:

[Update:]
Reading through them again, if you're short of time the 2nd article can probably stand on it's own.

To summarize: most "threadsafe" collections you find posted online are generally either not very useful or only thread safe one operation at a time, making "decision" members like .Count or .Contains useless (they're "stale" by the time the next line of code runs).

Joel Coehoorn
+1  A: 

When you enumerate over the collection of KVP you are dipping into and out of the lock for the entire dictionary. So in the loop body of your enumeration you are not in the lock, and changing the KVP could cause a race condition.

grieve
+1  A: 

I can answer that question directly, since I wrote that dictionary :)

Obtaining an enumerator on the dictionary will get a reference to the internal KVP sets. You would have to hold the read lock until you are done enumerating, which could be a long while.

The reason I say to enumerate the keys or values instead, is that I copy the list of keys and values when it is requested, therefore I am not returning a reference to the dictionaries internal list, which if you altered out of lock, could cause issues. Although, I suppose you could implement the same pattern on the KVP's as well if you wanted since they are read only.

    public virtual ICollection<TKey> Keys
    {
        get
        {
            using (new ReadOnlyLock(this.dictionaryLock))
            {
                return new List<TKey>(this.dict.Keys);
            }
        }
    }

    public virtual ICollection<TValue> Values
    {
        get
        {
            using (new ReadOnlyLock(this.dictionaryLock))
            {
                return new List<TValue>(this.dict.Values);
            }
        }
    }
Brian Rudolph
Couldn't you copy the internal list as well?
Svish
ahh, beat me to it
Brian Rudolph
the real trick to that is that you still couldn't use the enumerator, as it is stateful. If 2 threads attempted to enumerate at the same time, where would you store the copied list?
Brian Rudolph
I was thinking more like, Lock -> Copy array -> UnLock -> then return enumerator on copied version.
Svish
Not a bad plan. Though it does violate one of the implied properties of enumeration, in that the list you are enumerating should not change during enumeration. Either way, as long as that is understood, I guess it wouldn't hurt.
Brian Rudolph