A little trick when working with ConcurrentDictionary
A little trick when working with ConcurrentDictionary
ConcurentDictionary has one specific feature: in some cases it may not behave exactly as you'd expect. Here is a small example. Let's say we need to do some small caching so that the results of an expensive calculation be taken from the cache if they are there, and be added to the cache in a transparent manner if someone made a mistake.
22 Jul 2015
13049
Other articles
How to debug and solve the issue of Datanodes which were reported as Bad nodes?
How to incrementally migrate the data from RDBMS to Hadoop using Sqoop Incremental Last Modified technique?
How to implement Slowly Changing Dimensions(SCD) Type 2 in Spark?
How to incrementally migrate the data from RDBMS to Hadoop using Sqoop Incremental Append technique?
Why MongoDB don't fetch all the matching documents for the query fired
How to solve the issue of full disk utilization in HDFS Namenode
Can We Use HDFS as Back-up Storage?
How to do Indexing in MongoDB with Elastic Search? Part 1
How to do Indexing in MongoDB with Elastic Search? Part 2
How to store data on browser using NoSQL IndexedDB?
ConcurentDictionary has one specific feature: in some cases it may not behave exactly as you'd expect. Here is a small example. Let's say we need to do some small caching so that the results of an expensive calculation be taken from the cache if they are there, and be added to the cache in a transparent manner if someone made a mistake.
A simple implementation of a provider with a cache aside pattern would look like this:
In terms of multi-threading, this implementation is absolutely correct. Even if method RunOperationOrGetFromCache for the same operationId was invoked from two threads, each of them will get the same result. The problem, however, is that, although the result will be the same, we will have two operations running. The result of the first operation will be placed in the cache and the result of the second operation will be thrown away!
The reason lies in implementing the method AddOrGet of class ConcurrentDictionary. In fact, the use of AddOrGet is equivalent to the consistent use of methods like TryGetValue TryAdd in our own code (method AddOrGet is a little more complicated than a simple invocation of these two methods):
Now, it should be clear that if two threads are going to conflict and simultaneously try to get the results of the same operation, a long term operation will be run twice.
However, it's not all that bad. Since the competitive invocation of method AddOrGet will place only the first result in the collection, you can use the following trick:
Instead of only storing the result of a long operation, the cache will also store the "lazy shell" - Lazy . In this case, during simultaneous referring to the cache from multiple threads only a constructor of object Lazy <T>, , will be invoked several times and the operation itself will be run only once - when accessing the property Value!
Sergey Teplyakov
Expert in .Net, С++ and Application Architecture
A simple implementation of a provider with a cache aside pattern would look like this:
In terms of multi-threading, this implementation is absolutely correct. Even if method RunOperationOrGetFromCache for the same operationId was invoked from two threads, each of them will get the same result. The problem, however, is that, although the result will be the same, we will have two operations running. The result of the first operation will be placed in the cache and the result of the second operation will be thrown away!
The reason lies in implementing the method AddOrGet of class ConcurrentDictionary. In fact, the use of AddOrGet is equivalent to the consistent use of methods like TryGetValue TryAdd in our own code (method AddOrGet is a little more complicated than a simple invocation of these two methods):
Now, it should be clear that if two threads are going to conflict and simultaneously try to get the results of the same operation, a long term operation will be run twice.
However, it's not all that bad. Since the competitive invocation of method AddOrGet will place only the first result in the collection, you can use the following trick:
Instead of only storing the result of a long operation, the cache will also store the "lazy shell" - Lazy . In this case, during simultaneous referring to the cache from multiple threads only a constructor of object Lazy <T>, , will be invoked several times and the operation itself will be run only once - when accessing the property Value!
Looking to upgrade your programming skills? Check out our trainings.
Sergey Teplyakov
Expert in .Net, С++ and Application Architecture