2

I want to create a ConcurrentDictionary to store some data in my project, and want to keep it during all my app's lifetime,so it can be access by different thread to get the cached data.

I must make it's initialization thread-safe, I don’t want it to be created twice when some thread access it at the same time.

I know that the static field be initialized when it be accessed first time.So I define the ConcurrentDictionary like this:

public class CacheData
{
    public readonly static ConcurrentDictionary<string, string> ConcurrentDic = new ConcurrentDictionary<string, string>();
}

As far as i know, it's a singleton, and it's thread-safe, so what confuses me most now is I cannot find any reason to use any other solution to create a singleton such as a Lazy<ConcurrentDictionary<string, string>> like:

public class CacheData
{
    private static Lazy<ConcurrentDictionary<string, string>> _concurrentDic = new Lazy<ConcurrentDictionary<string, string>>(() =>
    {
        return new ConcurrentDictionary<string, string>();
    });

    public static ConcurrentDictionary<string, string> ConcurrentDic
    {
        get
        {
            return _concurrentDic.Value;
        }
    }
}

If Lazy<T> can be completely replaced by static field here, when will Lazy<T> be used?

Looking forward to your answer

Hanabi
  • 577
  • 4
  • 9
  • 1
    Your lazy dictionary is initialized once the **property** is used the first time, whereas the `static readonly` field is inisitialized when the **class** is used the very first time. So those two aren´t equivalent. Also see this post for thread-safety on static members: https://stackoverflow.com/questions/12159698/thread-safety-on-readonly-static-field-initialisation – MakePeaceGreatAgain Jul 01 '21 at 07:07
  • @HimBromBeere the static readonly field is inisitialized when **I first use the dictionary**, and I wrote a test program[Sample of Static Field](https://gist.github.com/GadHao/d57da32fb325a9ace74123698211201c) – Hanabi Jul 01 '21 at 07:18
  • 1
    For a lightweight object like a dictionary, then using a static variable is fine. But what if you have an object which takes 20 seconds to create, but you want to ensure you create only _one_ of them? Show us how you would do that _without_ `Lazy` or `LazyWithNoExceptionCaching` (https://stackoverflow.com/a/42567351/34092). **That** is why `Lazy` is useful - not because you need it everywhere, but because when you need it **you need it**. – mjwills Jul 01 '21 at 07:18
  • 3
    `the static readonly field is inisitialized when I first use the dictionary,` That is an overly simplistic explanation. With your code _right now_ that might be true. It isn't _always_ true for all code and all runtimes. Your sample is simple, and so it acts simply. https://dotnetfiddle.net/l7lB1Y – mjwills Jul 01 '21 at 07:18
  • @mjwills It just output once `The Dictionary has been initialized now` in this sample code [multithread inisitialize a static field](https://gist.github.com/GadHao/3b784d0d895849ec76f6115c3e8090a9) – Hanabi Jul 01 '21 at 07:25
  • 2
    @HaoGuo Ask yourself how `Bob has been called` was written to the console in my link without me using the variable. My link _disproves_ your hypothesis. You can clearly have a static field populated _without accessing it_. Also see https://dotnetfiddle.net/HhC6Gt . Now if you run my most recent dotnetfiddle on .NET 5, then yes the order is changed. But that is my point - you are relying on something that cannot be relied on. It _might_ act the way you say it does. it _might not_. – mjwills Jul 01 '21 at 07:26
  • 2
    just imagine you´d had a second static member. If you´d use your dictionary the first time, that second member would **also** be initialized, whereas `Lazy` would only initialize the member you´re calling. – MakePeaceGreatAgain Jul 01 '21 at 07:29
  • @mjwills I'm viewing the link page, thank you – Hanabi Jul 01 '21 at 07:29
  • @mjwills yes, when I run your code or my code in my `.NET Core 3.1` or `.NET 5 ` environment, it behave like I said, but when I run it in `.NET Framework`(In addition, I use 4.7.2) or in your link's code playground, it's not like this at all. I think now I am very clear about the difference between them, **you are relying on something that cannot be relied on.** fantastic answer!!! thank you!!! – Hanabi Jul 01 '21 at 07:45
  • @mjwills hi mjwills, would you like to summarize your comments into one answer? I think this is what i want, if you have no time to do that, maybe I will do it later – Hanabi Jul 01 '21 at 10:11

2 Answers2

2

First off you´re not really using a singleton here. A singleton is just the one and only instance of a specific class. A static field in itself has no instance at all. There might be dozens of ConcurrentDictionary within your code and also a dozens instances of your class.

static just means: all instances - regardless on how many of those exist - share the same member, in your case the dictionary.

Apart from this your two appraoches aren´t equivalent, even though in your specific example they seem similar. The most important difference between a static readonly field and a static Lazy field, is that the former is initialized the very first time your class is used, whereas the latter is initialized when that member is used the very first time. Chances are the lazy member isn´t called in your entire code, so it wont be initialized at all.

Two make this more clear see this example:

public class CacheData
{
    private static readonly int myint = 3;
    private static Lazy<ConcurrentDictionary<string, string>> _concurrentDic = new Lazy<ConcurrentDictionary<string, string>>(() =>
    {
        return new ConcurrentDictionary<string, string>();
    });

    public static ConcurrentDictionary<string, string> ConcurrentDic
    {
        get
        {
            return _concurrentDic.Value;
        }
    }
}

Now you have two completely independ members here. While myint is initialized when your class is used, the dictionary stays un-initialized. Only when you try to access that dictionary it will be initialized:

class MyClass
{
    void DoSomething()
    {
        // this will initialize myint if not already done
        CacheData.DoSomething(); 
        // however the dictionary is not initialized, only if you try to access it
        console.WriteLine(CacheData.ConcurrentDic["Hello"]);
        // now the dictionary is also initialized
    }
}

To keep long stories short: Lazy is ment to defer (heavy) initialization as far as possible until needed, whereas a static readonly field will be initialized on class-initialization.

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • `is that the former is initialized the very first time your class is used,` I think it is slightly more subtle than that (hence how different runtimes can initialise it at different times). – mjwills Jul 01 '21 at 07:47
  • Yes, I understand how `Lazy` works. I think I have got the answer I want because of the difference in the performance of static fields initialization at different runtimes. like @mjwills said: **relying on something that cannot be relied on.** – Hanabi Jul 01 '21 at 07:52
2

Fundamentally, you are seeing behaviour that isn't guaranteed. Because it isn't guaranteed, you can't rely on it. Take the below code as an example. What output will it generate? The answer is it depends.

17.4.5.1: "If a static constructor (§17.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class."

implementation-dependent means multiple orders are valid, as long as the rules are kept.

It might generate:

The Dictionary has been initialized now
Start Running...
Start Running...

Or it might generate:

Start Running...
Start Running...
The Dictionary has been initialized now

You can validate it yourself at https://dotnetfiddle.net/d5Tuev (switch runtimes on the left side).

Now, in your testing you have experienced one of those behaviours. But that behaviour isn't guaranteed, and thus you shouldn't rely on it.

using System.Collections.Concurrent;
using System.Threading;

namespace Samples.Console
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Thread thread1 = new Thread(() => StaticField.StaticFieldTest());
            Thread thread2 = new Thread(() => StaticField.StaticFieldTest());

            thread1.Start();
            thread2.Start();

            Thread.Sleep(1000);
        }

        public class StaticField
        {
            public static readonly ConcurrentDictionary<string, string> _dic = InitDictionary();

            public static void StaticFieldTest()
            {
                System.Console.WriteLine("Start Running...");

                _dic.TryAdd(string.Empty, string.Empty);
                _dic.TryAdd(string.Empty, string.Empty);
                _dic.TryAdd(string.Empty, string.Empty);
            }

            public static ConcurrentDictionary<string, string> InitDictionary()
            {
                System.Console.WriteLine("The Dictionary has been initialized now");
                Thread.Sleep(500);
                return new ConcurrentDictionary<string, string>();
            }
        }
    }
}

In addition - Lazy or LazyWithNoExceptionCaching are quite useful in some contexts - e.g. if you have an object which takes 20 seconds to create, but you want to ensure you create only one of them.

mjwills
  • 23,389
  • 6
  • 40
  • 63