Still in these days you once in a while have use for a singleton object. But how do you achieve a thread-safe singleton that requires async initialization?
First, let’s start off with a good ol’ singleton class that can be constructed synchronously.
Synchronous Singleton
Assuming your singleton class needs some initialization data that it can construct itself (synchronously). To make it thread-safe, we rely on locking on a static read-only object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
public class MySingleton { private static readonly object SyncObj = new object(); private static MySingleton _singleton; /// <summary> /// The public available singleton instance. /// </summary> public static MySingleton Singleton { get { if (_singleton == null) { lock (SyncObj) { if (_singleton == null) { _singleton = CreateSingleton(); } } } return _singleton; } } /// <summary> /// The private instance-constructor, taking some data. /// </summary> private MySingleton(SomeData someData) { SomeData = someData; } /// <summary> /// Some data that the singleton exposes. /// </summary> public SomeData SomeData { get; } /// <summary> /// Create the singleton instance. /// </summary> private static MySingleton CreateSingleton() { SomeData someData = CreateSomeData(); return new MySingleton(someData); } /// <summary> /// A private method that can create some data, called once. /// </summary> private static SomeData CreateSomeData() { // ...some way to create some data... } } |
In the version above the Singleton
property is designed to work as quick as possible, only entering the lock if there is a chance that the class hasn’t yet been initialized. Within the lock
, a method that creates the MySingleton
object is called. This way it’s ensured that only one instance of the MySingleton
is ever created.
Asynchronous Singleton
But what to do if the CreateSomeData
method would work asynchronous, returning a Task<SomeData>
instead, using the await
keyword? Let’s modify the code in this way:
1 2 3 4 |
private static async Task<SomeData> CreateSomeData() { // ...some way to create some data, using the await keyword... } |
Making this change, we should play nice and propagate the async
’ness to the CreateSingleton
method:
1 2 3 4 5 |
private static async Task<MySingleton> CreateSingleton() { SomeData someData = await CreateSomeData(); return new MySingleton(someData); } |
That would naturally imply that the Singleton
property should also be asynchronous, returning a Task<MySingleton>
instead.
So, is that possibly?
First of all, it turns out that it’s not allowed to use the async
keyword with a property. But this is no biggie, we could afford a method instead.
But more importantly, we are not allowed to use a lock
around an await
keyword. (This is actually a good thing, since the lock
would really work against us here – so we should thank the C# team for not allowing this!)
So how do we do this then? As a starter, a naïve, non thread-safe solution would look like this:
1 2 3 4 |
public static Task<MySingleton> Singleton { get { return CreateSingleton(); } // WOHAA! NOT SINGLETON AT ALL! } |
Requesting the Singleton
property would of course lead to a call to the CreateSingleton
method every time, and would hardly be a singleton…
But the solution to make it both a singleton and thread-safe turns out to be ridiculously simple – we 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. Now 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, a Task
is thread-safe, and it looks as if we could use this power in our advantage here!
In the end, all we need is to replace the old Singleton
property with this one:
1 |
public static Task<MySingleton> Singleton { get; } = CreateSingleton(); |
Or, rewritten in a more classic C# way (without making use of the fancy new C# 6 “read-only properties” and “property initializers”):
1 2 3 4 5 6 7 |
private static readonly Task<AsyncSingleton> CreateSingletonTask = CreateSingleton(); public static Task<AsyncSingleton> Singleton { get { return CreateSingletonTask; } } |
So the dead simple solution is to assign the async
method to a read-only (static) Task
field (or read-only property in C# 6). This gives you both a singleton and thread-safety for free!
Lazy Synchronous Singleton
Being empowered with the super-simple async
version, you might wonder if there really is no similar way to achieve this in the synchronous version?
Of course there is!
Revisiting the synchronous first version of the MySingleton
in this blog post, you may replace the SyncObj
and _singleton
fields and the Singleton
property with these two lines:
1 2 3 4 |
private static readonly Lazy<MySingleton> LazySingleton = new Lazy<MySingleton>(CreateSingleton); public static MySingleton Singleton => LazySingleton.Value; |
Or, if you prefer the pre-C# 6 code:
1 2 3 4 5 6 7 |
private static readonly Lazy<MySingleton> LazySingleton = new Lazy<MySingleton>(CreateSingleton); public static MySingleton Singleton { get { return MySingleton.Value; } } |
Wow, magic! No need for locks or anything! So how does this work?
Well, the Lazy<T>
can be seen as the synchronous counterpart to the Task<T>
in some aspects. At least it has the same singleton and thread-safety properties, and keeps its results for latecomers.
As you can see, the constructor of the Lazy<MySingleton>
is given the means of producing a MySingleton
(i.e. the CreateSingleton
method) – not an instance of the type directly. Then, when someone is requesting the Singleton
property, the Value
of the Lazy
-class is accessed. And in the same manner as the Task
, the first one reaching for the Value
will make the actual call to the CreateSingleton
method. Any other thread asking at the same time will simply be hanging in the Value
getter, and continues once the CreateSingleton
method is done and the Value
is produced. Further on, any consecutive calls to the Value
getter will return immediately with the same instance of the MySingleton
. So, again, the Lazy
gives us both a singleton and thread-safety.
And there was much rejoicing!