blog community
New coordination datastructures in .Net 4.0 Beta 2

To be honest a have not spent too much time looking at .Net 4.0 beta 2 yet. But I do keep up with the blogs concerning Parallel Extensions. Josh Philps has just posted an entry about the changes that were made in the Coordination Data Structures (CDS) in Beta 2. The CDS are the types that have been added to the framework that will help writing concurrent applications without having to do (to much of) your own synchronization. I used two of the CDS classes (ConcurrentDictionary(Tkey, TVAlue) and Lazy<T>) in the CacheDictionary that I wrote about in this article.

   1: public class CacheDictionary<TKey, TValue>   
   2:     where TValue : class // needs to be a ref type due to current limitation of lazyInit<>   
   3: {   
   4:     ConcurrentDictionary<TKey, LazyInit<TValue>> _cacheItemDictionary = new ConcurrentDictionary<TKey, LazyInit<TValue>>();   
   5:   
   6:     public TValue Fetch(TKey key, Func<TValue> producer)   
   7:     {   
   8:         LazyInit<TValue> cacheItem;   
   9:         if (!_cacheItemDictionary.TryGetValue(key, out cacheItem))   
  10:         {   
  11:             cacheItem = new LazyInit<TValue>(() => producer(), LazyInitMode.EnsureSingleExecution);   
  12:   
  13:             if (!_cacheItemDictionary.TryAdd(key, cacheItem))   
  14:             {   
  15:                 // while we never remove items, if TryAdd fails it should be present   
  16:                 cacheItem = _cacheItemDictionary[key];   
  17:             }   
  18:         }   
  19:         return cacheItem.Value;   
  20:     }   
  21: }  

In this cache I used ConcurrentDictionary<TKey, TValue> to store the cache items. To check if an item is present or should be created, I used TryAdd(key, cacheItem). This will return false if the item is allready present in the cache, in that case I assume the item can be retrieved from the dictionary with the indexer[]. This will work because the cache I implemented (unfortunately) does not yet support removals. If the cache would support any kind of mechanism to remove or invalidate items in the cache, the fetch method would have to handle the case of an item beeing removed between the TryAdd() and the retrieval.

The problem here is that up till beta 2 there was no good way to do the Add and "Get if allready present" in a single atomic operation. To work around this I would have to either use my own lock (that would remove all benefits of using a CuncurrentDictionary in the first place) or create a loop that keeps on calling TryAdd() and TryGetValue() until one of them succeeds.

I submitted this feedback to the parallel extensions forum with the suggestion to add an operation TryGetOrAddValue() that does these two operations in a single atomic method. I am pretty happy (and a bit proud :-) to see that this suggestion has found its way into the framework. The actual operation is now called GetorAdd(), (without the Try) which makes sense because either the Get or the Add will allways succeed. The PFX team really does seem to listen to custmer feedback!

As a bonus they actually added an overload for GetOrAdd which accepts a delegate that is executed only if the item was not yet in the dictionary. I have not yet looked into the details, but it looks like this does exactly what my CacheDictionary.Fetch operation did! This means that (unless I add support for cache expiration) my CacheDictionary will no longer be needed at all, as all its functionality has now been incorporated into the Framework! Now how Cool is that

 


Posted 12-11-2009 9:39 by frankb

Comments

horo wrote re: New coordination datastructures in .Net 4.0 Beta 2
on 11-01-2010 13:35

Hi Frank,

Thx for the idea of using Lazy<T> with ConcurrentDictionary.

However this implementation CacheDictionary leaves an unresolved problem open: producer() needs to know the "key" to create its return value. Unfortunatelly Lazy<T> does not provide a standard way to pass parameters to producer().

Using any inline implementation for producer(), and referencing the "key" local variable of Fetch method of course not a solution, because the return cacheItem.Value;   statement potentiali can run in an other thread, with other actual "key" parameter than the thread which added the cacheItem to the dictionary.

frankb wrote re: New coordination datastructures in .Net 4.0 Beta 2
on 12-01-2010 8:56

Hi Hero, thanks for your commens

What I did not describe in tis or any of my previous posts on this subject is the usage pattern for this style of cache. This might be a tipic for a next post.

In the way I use this cache, I actually do use an inline delegate for the producer that captures the key and potentially any other required variables from the surrounding scope to create the new value. This should not be a problem, because the cachekey is also used as the index in the underlying ConcurrentDictionary, so each different key will have a different instance of Lazy<T>.

The new GetOrAdd operation on ConcurentDictionary does solve this problem by providing the key to the producer delegate. You could just use ConcurrentDictionary directly without the need for my CacheDictionary at all. The only thing you do net get from ConcurrentDictionary.GetOrAdd() is the guarantee thet the producer will be executed exectly once, depending on your scenario that might or might not be a problem.

Add a Comment

(required)  
(optional)
(required)  
Remember Me?
Enter code (required)
Powered by Community Server (Commercial Edition), by Telligent Systems