Thursday, November 12, 2009

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>>();   
   6:     public TValue Fetch(TKey key, Func producer)   
   7:     {   
   8:         LazyInit cacheItem;   
   9:         if (!_cacheItemDictionary.TryGetValue(key, out cacheItem))   
  10:         {   
  11:             cacheItem = new LazyInit(() => producer(), LazyInitMode.EnsureSingleExecution);   
  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

No comments:

Post a Comment