Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Singleton pattern in a multiple worker thread Application Pool

0.00/5 (No votes)
13 Nov 2012 1  
This article is about cross-worker thread solutions of implementing the Singleton Pattern in ASP.NET.

Introduction

As I've learned during my programming experience, Singleton is a very useful pattern and finds its use in nearly every big project – especially if it's a multi threaded, multi user application. For quite a while recently, I was working mostly on web-applications created in C# in the ASP.NET Framework. And, as it turns out, Singletons in web environment aren't always that straightforward to create and use.

In web-applications, we might be considering the usage of three kinds of singletons:

  • Single instance per web-request
  • Single instance per user (session)
  • Single instance per whole web application

The first two cases aren't really an issue; to be honest, I'd probably argue if single instance per web-request would still be called singleton, but this of course depends on the complexity of the web-request. What I'm really interested in is the third point, which is “Single instance per whole web application” which might be little tricky sometimes.

Usually, the standard Singleton pattern would apply as by default there is only one worker. A problem I've faced a couple of times during a few of my projects is - how to implement a Singleton in a multiple worker threads environment. If we have only one worker thread, implementing Singleton isn't really an issue as all web requests will share a static instance of it across the whole worker thread; the only problem is how to secure it as it's still a multi threaded environment, and this can be simply prevented by adding a "lock" as in the example below.

public class Singleton {
    static Singleton instance = null;
    static readonly object padlock = new object();

    Singleton() { }

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

This will work well when we use one worker process in the application pool, but these days, most of the current server platforms do have multi-core processors, and with one working process, we won't really get the most of it. Another disadvantage of having one worker thread is when one of the requests is running slow, all other requests will be directly affected by that. What we can do to upscale the performance is raise the amount of worker threads. But unfortunately, static variables are only shared across one worker process, so we would end up with multiple "singletons", one per each process – what in some cases might be acceptable, but often it might become a big problem (there might be access violations, for example), and in the end, it doesn't really follow the singleton pattern any more.

.NET Remoting Singleton

An example of a Singleton for which multiple working processes might be an issue is the File Logger class – when each request would write some data to the same file. We could imagine what would happen if two "singletons" would try to write to the same file at the same time.

To solve this problem, we have to communicate between worker threads so all of them would be accessing a singleton which would exist in only one of them.

To accomplish that, we can use a .NET Remoting system (more details: here).

The idea is that a Singleton HTTP server channel will be created at the first attempt to access the GetInstance() method, so we'll have full lazy instantiation (like in the first code example). If the server channel is already created (what we can figure out by catching Socket Exception: second code block), the worker thread will attempt to connect to it to create the proxy that will allow us to access the singleton.

try {
     channel = new HttpChannel(8089);
    ChannelServices.RegisterChannel(channel, false);
    RemotingConfiguration.RegisterWellKnownServiceType(typeof(Singleton), 
                "Singleton", WellKnownObjectMode.Singleton);
    instance = (Singleton)Activator.GetObject(typeof(Singleton), 
            "http://localhost:8089/Singleton"); 
} catch (SocketException) {
    channel = new HttpChannel();
    ChannelServices.RegisterChannel(channel, false);
    instance = (Singleton)Activator.GetObject(typeof(Singleton),
            "http://localhost:8089/Singleton"); 
}

In this example, we use hardcoded values for the port number and the service name – it's probably a better idea to keep those in the Web.Config.

So, if on the server we already have created an instance of the server channel on a particular port number - new HttpChannel(8089) will throw a Socket Exception, so skip the server creation part and attempt to get the current instance from the existing channel.

We might have a problem if the Channel is created but the Singleton isn't fully registered yet. If another working thread will attempt to connect to it – it will fail. This is kind of tough to resolve – so in my opinion, we should try to avoid making huge constructors. If we have a constructor that takes a while, it might be a good exercise to figure out the worst case scenario and add a loop with delay to allow some time in case the singleton isn't created yet (third code block).

for (int i = 0; i <= 6; i++) {
    try {
        instance = (Singleton)Activator.GetObject(typeof(Singleton),
                     "http://localhost:8089/Singleton"); 
        break;
    } catch (RemotingException) {
        Thread.Sleep(300);
    }
}

Another problem is - what to do when the main worker thread (the one that created the singleton and the server channel) would die?

The IIS Application Pool settings allows you to specify the idle time and we could maximize this to prevent it from happening, but that's probably not the best idea as if anything would go wrong in a particular worker thread, it might still crash, and again we'll lose our singleton, and proxies in other threads would be referring to the object that doesn't exist any more.

The way of solving that problem I figured is to check if the connection and the object is valid before we return an instance to the GetInstance() caller. To accomplish that, I've created a dummy CheckConnection() (example below) method that doesn't really do anything other than returning a boolean value. If this method will throw a socket exception, we'll know that the reference is no longer valid.

public bool CheckConnection() {
    return true; 
}

So when we know that the singleton server connection has died – we can create a new one in the current worker thread.

More Thoughts

This, of course, isn’t the only solution to this problem. We might create a Windows service with some kind of interface, Web Services working on one worker thread that our application would communicate with and probably many others. The main advantage that I find very useful in the solution I’ve described is maintainability – we don’t have to create any other application pool, Web Services, or registering and making sure that the Windows Service is working properly. It’s also pretty bulletproof; most of the exceptions would be handled in the GetInstace() method. Another convenience is the usage of it from a programmer perspective – we don’t have to initiate a connection or make sure there is some instance created somewhere already, as again it’s all handled by the GetInstace() method.

All of those pros I find very profitable in an environment as mine when you have to maintain a huge amount of small and medium applications. We might argue if .NET Remoting is the best solution from a performance perspective for singletons, but I think it’s a compromise – the application would be much more scalable if it would work on multiple worker threads, but then, we have to use remoting for some parts of the application; of course, it’s for us to decide when it may or may not affect performance. It would probably perform better through TCP or IPC binary channels, but unfortunately, IIS only supports HTTP.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here