MBZ
MBZ

Reputation: 27592

Singleton Class which requires some async call

I have a Singleton Class which loads some data on its construction. The problem is that loading this data requires calling async methods, but the constructor cannot be async.

In other words, my class has following structure:

public class Singleton
{
   private static Singleton instance;

   private Singleton() 
   {
       LoadData();
   }

   public static Singleton Instance
   {
      get 
      {
         if (instance == null)
         {
            instance = new Singleton();
         }
         return instance;
       }
    }
}

LoadData() is an async function which calls lots of async functions as well as initialization. How can I call LoadData() properly so everything initialize correctly?

Upvotes: 19

Views: 19060

Answers (4)

Mattias Larsson
Mattias Larsson

Reputation: 823

The solution for a thread-safe, async singleton is actually super simple, if we only let the inner mechanisms of the Task class work for us!

So, how does a Task work? Let’s say you have an instance of a Task<T> and you await it once. Now the task is executed, and a value of T is produced and returned to you. What if you await the same task instance again? In this case the task just returns the previously produced value immediately in a completely synchronous manner.

And what if you await the same task instance simultaneously from multiple threads (where you would normally get a race condition)? Well, the first one (since there will be one that gets there first) will execute the task code while the others will wait for the result to be processed. Then when the result has been produced, all the await’s will finish (virtually) simultaneously and return the value.

So the solution for an async singleton that is thread-safe is actually super simple:

public class Singleton
{
    private static readonly Task<Singleton> _getInstanceTask = CreateSingleton();

    public static Task<Singleton> Instance
    {
        get { return _getInstanceTask; }
    }

    private Singleton(SomeData someData)
    {
        SomeData = someData;
    }

    public SomeData SomeData { get; private set; }

    private static async Task<Singleton> CreateSingleton()
    {
        SomeData someData = await LoadData();
        return new Singleton(someData);
    }
}

Now you can access the singleton this way:

Singleton mySingleton = await Singleton.Instance;

or

Singleton mySingleton = Singleton.Instance.Result;

or

SomeData mySingletonData = (await Singleton.Instance).SomeData;

or

SomeData mySingletonData = Singleton.Instance.Result.SomeData;

Read more here: Async singleton initialization

Upvotes: 30

Peter Ritchie
Peter Ritchie

Reputation: 35870

Well, it doesn't make much sense that you want asynchronously initialize a singleton. If you simply want to call an method that returns Task in your initialization, you can simply do:

var task = MyAsyncMethod();
task.Wait();
return task.Result;

Without the need to make the method async.

But, if what you want is for the singleton value to be a task, you can use Lazy as such:

Lazy<Task<int>> l = new Lazy<Task<int>>(async () => { int i = await calculateNumber(); return i; });

In addition, Lazy<T> is the preferred method for implementing "singletons". Singleton classes are hard to get right (or hard to keep right)...

Upvotes: 1

Stephen Cleary
Stephen Cleary

Reputation: 456657

You can use asynchronous lazy initialization for this:

public class Singleton
{
  private static readonly AsyncLazy<Singleton> instance =
      new AsyncLazy<Singleton>(CreateAndLoadData);

  private Singleton() 
  {
  }

  // This method could also be an async lambda passed to the AsyncLazy constructor.
  private static async Task<Singleton> CreateAndLoadData()
  {
    var ret = new Singleton();
    await ret.LoadDataAsync();
    return ret;
  }

  public static AsyncLazy<Singleton> Instance
  {
    get { return instance; }
  }
}

And then you can use it like this:

Singleton singleton = await Singleton.Instance;

One benefit of using AsyncLazy<T> is that it is threadsafe. However, be aware that it always executes its delegate on a thread pool thread.

Upvotes: 9

Reed Copsey
Reed Copsey

Reputation: 564451

The problem is that loading this data requires calling async methods, but the constructor cannot be async.

While you can't make the constructor itself asynchronous, you can call asynchronous methods from within the constructor. You just will not get the results back immediately.

Provided the asynchronous methods return Task or Task<T>, you can always use a continuation on the task to set your data within the class once the asynchronous operation completes, or just block on the results, depending on what makes the most sense in your scenario. Without knowing the requirements for construction of this object, it's difficult to know what is appropriate in this scenario.


Edit:

One option, given the goals listed above, would be to change your Singleton declaration so that method to retrieve the Instance was a method, not a property. This would allow you to make it asynchronous:

public class Singleton
{
   private static Singleton instance;

   private Singleton() 
   {
          // Don't load the data here - will be called separately
   }

   public static async Task<Singleton> GetInstance()
   {
         if (instance == null)
         {
            instance = new Singleton();
            await instance.LoadData();
         }

         return instance;
    }
}

This would allow you to use await on the call to actually retrieve the instance. The nice thing about this is that it does make it very clear that you're calling an asynchronous operation, and you will get proper handling of the results, as the result will come back like any other async method.

Be aware, however, that this isn't thread safe (though the original wasn't either), so if you're going to use this Singleton from multiple threads, you may have to rethink the overall design.

The other option would be to make your Singleton class not automatically load data. Make the methods that retrieve the data from the class asynchronous, instead. This provides some real advantages, as the usage is probably a bit more standard, and you can support calls from multiple threads a bit more easily (since you can control the data loading process) than you'd be able to handle it with making the access of the class instance asynchronous.

Upvotes: 15

Related Questions