-2

I have a static class that gets me a client..

public static ClientFactory {

    private static Lazy<IClient> _MyClient;

    public static IClient GetClient(ICache cache) {

        if (_MyClient == null) {
            _MyClient = new Lazy<IClient>(() => CreateClient(cache));
        }

        return _MyClient.Value;
    }

    private static IClient CreateClient(ICache cache) {
        // code that takes 1-2 seconds to complete
        return new Client(cache);
    }
}

Is there any chance that I can have 2 or more clients created by writing code like this? Where the second client would overwrite the first one?

How should I update my code in a way, such that the constructor is called only once per application?

Thanks.

Zhen Liu
  • 7,550
  • 14
  • 53
  • 96
  • Does this answer your question? [Are static methods thread safe](https://stackoverflow.com/questions/1090650/are-static-methods-thread-safe) and [How to make static method thread safe?](https://stackoverflow.com/questions/16976618/how-to-make-static-method-thread-safe) and [What does MethodImplOptions.Synchronized do?](https://stackoverflow.com/questions/2223656/what-does-methodimploptions-synchronized-do) and [C# : What if a static method is called from multiple threads?](https://stackoverflow.com/questions/3037637/c-sharp-what-if-a-static-method-is-called-from-multiple-threads) –  Jun 19 '21 at 07:35
  • `Is there any chance that I can have 2 or more clients created by writing code like this?` Yes. – mjwills Jun 19 '21 at 07:41
  • 1
    Also, be aware that the default `Lazy` caches exceptions, which is very unlikely to be the behaviour you want. So if `CreateClient` has _any_ possibility of throwing an exception you should consider using https://stackoverflow.com/a/42567351/34092 . – mjwills Jun 19 '21 at 07:42

2 Answers2

2

Yes, your code can result in multiple Client objects created. Use a static constructor to create the Lazy<IClient> instance:

public static ClientFactory
{
    ...

    static ClientFactory()
    {
        _myClient = new Lazy<IClient>(() => CreateClient());
    }

    public IClient GetClient() => _myClient.Value;
}

Example execution from your code:

  1. thread A evaluates _myClient == null as true, enters block
  2. thread B evaluates _myClient == null as true, enters block
  3. thread A creates Lazy<IClient> object and sets _myClient field
  4. thread A exits if block
  5. before thread B sets _myClient field with a new Lazy<IClient> object, thread A accesses _myCilent.Value property, resulting in the creation of your Client object
  6. thread B sets the _myClient field to its new Lazy<IClient> object
  7. thread B accesses the _myClient.Value property, creating a second Client object
  8. all subsequent calls to GetClient will return this second instance of Client
Moho
  • 15,457
  • 1
  • 30
  • 31
  • 3
    Why use a static constructor? Why not just assign on the same line as the declaration? `private static Lazy _MyClient = new Lazy(() => CreateClient());` – mjwills Jun 19 '21 at 07:52
  • so, in this case, if many calls are made to do _MyClient.Value am I guaranteed to have created only 1 instance? – Zhen Liu Jun 19 '21 at 08:19
  • yes as the static constructor is guaranteed to execute only once per application domain and will execute before any other method in the class can be externally called – Moho Jun 19 '21 at 08:45
  • @mjwills yes, inline initialization would produce the same results in this case – Moho Jun 19 '21 at 08:48
  • So, what should I do my GetClient call needs to have some objects passed in? How do I do that with a single static constructor? – Zhen Liu Jun 19 '21 at 23:37
  • I guess I will use a lock() in my constructor code then. – Zhen Liu Jun 20 '21 at 02:03
1

Yes, there is a chance, consider this example, based on your input, a run of this simple program may result in initialization of one or two clients (run multiple times to see):

void Main()
{
    System.Threading.Tasks.Parallel.Invoke(
        () => ClientFactory.GetClient(1),
        () => ClientFactory.GetClient(2));  
}

// You can define other methods, fields, classes and namespaces here
public static class ClientFactory {

    private static Lazy<IClient> _MyClient;

    public static IClient GetClient(int init) {
        Console.WriteLine(_MyClient == null);
        if (_MyClient == null) {
            Console.WriteLine(init);
            _MyClient = new Lazy<IClient>(() => CreateClient());
        }       
        return _MyClient.Value;
    }

    private static IClient CreateClient() {
        // code that takes 1-2 seconds to complete
        return new Client();
    }
}

public interface IClient {

}

public class Client : IClient {
    public Client() {
        Console.WriteLine("Client constucrtor");
    }
}

Two threads may be within the if (_MyClient == null) clause in the same time, creating multiple objects, only one assigned.

Ofiris
  • 6,047
  • 6
  • 35
  • 58