0

This is a question coming off another discussion I had, however I was curious what would happen in the following situation when using a concurrent dictionary and the yield functionality.

IEnumerable<int> GetValuesNotZero()
{
    foreach(int value in dictionary.Values)
    {
        if(value != 0)
        {
            yield return value;
        }
    }
}

If another thread adds/delete/update from the dictionary, what would happen with the values in the foreach loop? Does the dictionary get locked while I am iterating over it, or do I still run the risk of missing added/deleted/updated values?

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
ShaffDaddy
  • 63
  • 1
  • 9

1 Answers1

2

While you're iterating, if another thread modified the dictionary, you'll find the updated values as opposed to exception(with normal Dictionary<,>).

If you've not yet reached the modified item, you'll eventually walk through it; otherwise, you may miss the updated value if you already visited the updated element.

From ConcurrentDictionary.GetEnumerator

The enumerator returned from the dictionary is safe to use concurrently with reads and writes to the dictionary, however it does not represent a moment-in-time snapshot of the dictionary. The contents exposed through the enumerator may contain modifications made to the dictionary after GetEnumerator was called.

For example assume Thread1 is walking through the dictionary is now at position 3. If Thread2 modified element at position 7 you'll find the modified value. On the other hand, if it modified the element at position 1, you'll not notice it. (Forget the fact that dictionary is unordered; positions are used for better understanding).


I just realized that your code loops through Dictionary.Values(Thanks @Svick for mentioning in comments); Answer above is only true if you loop through the dictionary. When you're iterating through Values property, you're actually looping through a snapshot. If dictionary is updated in the mean time, you'll not notice it.

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • But the code in question isn't iterating the dictionary, it's iterating `Values`. – svick Mar 17 '15 at 13:58
  • @svick Thanks. Updated my answer. It turns out that `Values` property just returns a snapshot. – Sriram Sakthivel Mar 17 '15 at 14:04
  • Ah ok, so if I am using Keys/Values to iterate over I am guaranteed to get a snapshot, however if I am iterating over the entire collection no guarantee. That is really good news! I was wanting to use concurrent collections to my advantage instead of locking and not being able to use the yield return to my advantage. Yield is superior to the normal way, creating a local copy and then return its elements? – ShaffDaddy Mar 17 '15 at 14:28
  • @ShaffDaddy This is an implementation detail that Keys/Values property returns a snapshot. Which can be changed(as I can't find the doucmentation for it). So, don't depend on that behavior. What you can depend on is the thread safety, not the data what you get. Yield isn't superior; if you just iterate it with `foreach` also, the behavior is same. – Sriram Sakthivel Mar 17 '15 at 14:35
  • @Sriram: Ok so there is no performance advantage for using yield vs local copy? What if I have a huge set of values? Would this get the same performance: (code above) or List localCopy = new List(), then individually call localCopy.Add(int) and return localCopy. – ShaffDaddy Mar 17 '15 at 16:05
  • @ShaffDaddy There is a huge difference. Iterator blocks(yield keyword) is lazy. If you use yield, you can stop the iteration anywhere you want; further elements will not be processed. That will be tremendously helpful if gathering data is expensive, even you can avoid memory to hold the local copy of list etc. You may find my blog post about laziness of iterators helpful [Oh that Iterators!! They are damn lazy](https://sriramsakthivel.wordpress.com/2015/02/01/oh-that-iterators-they-are-damn-lazy/). – Sriram Sakthivel Mar 17 '15 at 17:11