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!
Looking to upgrade your programming skills? Check out our trainings.
Expert in .Net, Ñ++ and Application Architecture