There are a lot of threading issues that are fairly obvious which careful and conservative use of locks will solve. Conservative use is import because if we use locks too liberally we run the risk of turning all the work we’ve done to multi-thread back into a single thread scenario.
The subtle issues are the ones that get you when you least expect it. Things can run fine in unit tests and even through QA but get into production and things just don’t seem to work right. In my previous post I pointed out using an empty delegate so we don’t have to worry about when threads subscribe and unsubscribe from our events.
In this singleton example there is a bad problem with threading where we can run the potential issue of creating multiple instances of our class when we only want one.
private static MyClass _instance = null;
public static MyClass Instance
{
get
{
if(_instance == null)
_instance = new MyClass();
return _instance;
}
}
As you can see if two or more threads were to enter the “get” at the same time all of them could get past the the check for null with _instance. This would result in each thread causing an instance of MyClass to be instantiated. As an alternative you could wrap the check for a null _instance in a lock. There is, however, an easier way.
By using the System.Lazy class you can save your processor some work of having to create an object that may never be used as well as making the code fairly simple.
The simple case is as follows:
private static readonly Lazy<MyClass> _lazyInstance = new Lazy<MyClass>(() => new MyClass());
public static MyClass Instance
{
get { return _lazyInstance.Value; }
}
By utilizing Lazy the instance is never instantiated until it is needed. Additionally Lazy takes care of creating MyClass for us in a thread-safe manner and is a bit less code that we need to worry about.
In the more complex case where you may need to do more then just create the object, you can define a delegate that performs other steps for the initialization and returns the class:
private static readonly Lazy<MyClass> _lazyInstance = new Lazy<MyClass>(() =>
{
MyClass myClass = new MyClass(MyClass.SomeParameter);
// Perform additional initialization here.
return myClass;
});
The documentation warns that “IMPORTANT: Lazy initialization is thread-safe, but it doesn’t protect the object after creation.” So it will create the object in a thread-safe manner but after that it’s up to you to use it that way.
Thanks,
Brian