10

Scenario

Let's say we have:

var dictionary = new ConcurrentDictionary<string, Lazy<Heavy>>();

Instantiating Heavy is very resource-consuming. Let's consider this code:

return dictionary.GetOrAdd("key", key => 
{
    return new Lazy<Heavy>(() =>
    {
        return Instantiate();
    });
}).Value;

The method Instantiate() of course returns an instance of type Heavy.

Question

For a given key, is it 100% guaranteed that the method Instantiate() will be invoked at most once?

Sources

Some people claim that having multiple threads, we can only create multiple instances of Lazy<Heavy>, which is very cheap. The actual method Instantiate() will be invoked at most once.

I personally have an impression that this is false. What is the truth?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
ebvtrnog
  • 4,167
  • 4
  • 31
  • 59
  • 3
    Worth adding your thoughts about _why_ you think it is false. – Evk Nov 25 '17 at 18:32
  • @Evk, I basically think that if two separate threads can instantiate `Lazy` **twice**, they have access to two completely different objects. So getting `Value` wil yield `Instantiate` twice, as those `Lazy` instances have no shared information of this method being already invoked or not. – ebvtrnog Nov 25 '17 at 18:46
  • Just add some basic logging to `Instantiate()` and you will know better than anyone whether it's called once or twice – Camilo Terevinto Nov 25 '17 at 19:08
  • 2
    `I basically think that if two separate threads can instantiate Lazy twice, they have access to two completely different objects.` Two `Lazy` will be created, but both calls to `GetOrAdd` will get the same `Lazy` object (the other one will be effectively 'lost'). So it will execute only once. – mjwills Nov 26 '17 at 10:27

1 Answers1

13

Instantiate will indeed will execute only once. Documentation of GetOrAdd says:

If you call GetOrAdd simultaneously on different threads, addValueFactory may be called multiple times, but its key/value pair might not be added to the dictionary for every call.

What that means is: even if addValueFactory is run multiple times - only one of returned values will actually be added to dictionary and returned from GetOrAdd call. So if two threads call GetOrAdd at the same time with the same key - 2 Lazy<Heavy> instances are created, but only 1 instance is added to dictionary and returned from both GetOrAdd calls, the other is discarded (so, even if factory has been run - it does not mean value provided by this factory is what is finally returned from GetOrAdd). Because you call .Value on the result of GetOrAdd - you call that always on single instance of Lazy<Heavy> and so Instantiate always runs at most once.

Evk
  • 98,527
  • 8
  • 141
  • 191