Kamyar Nazeri
Kamyar Nazeri

Reputation: 26534

HTTPContext across threads

I need to instantiate a singleton object per web request, so that the data is processed once and is valid throughout the request, I was using HttpContext.Current.Items to share data during HTTP request, everything was fine until we needed the singleton object instance across multiple threads, the first thing that I came up with was to pass the HttpContext instance to the new thread:

HttpContext context = HttpContext.Current;
ThreadPool.QueueUserWorkItem(callback =>
    {
        HttpContext.Current = context;
        // blah blah
    });

Which I don't think is a thread-safe approach as noted here.

Using Reflector I figured HttpContext.Current.Items actually uses CallContext to store objects in each logical thread. So I changed the singleton interface to this:

public static SingletonType SingletonInstance
{
    get { return CallContext.GetData(key) as SingletonType; }
    set { CallContext.SetData(key, value); }
}

And simply overwrite SingletonInstance when starting any new thread! The code works fine, however it seems that somehow under heavy load, CallContext.GetData(key) returns null and the application crashes with with a null reference exception!

I was thinking, if CallContext.GetData is atomic? But it just doesn't seem right, the CallContext is thread specific data storage and must be atomic or I am missing the point!

My other guess is that setting the SingletonInstance (CallContext.SetData) happens in one thread while CallContext.GetData executes in another as noted here but I don't know how/why?

update:

We are keeping an instance of each online user in an array on the server. The singleton object is actually a reference to the object representing current user. Current user must be unique and available in each thread for database querying, logging, error handling and more, this is how it is done:

public static ApplicationUser CurrentUser
{
    get { return CallContext.GetData("ApplicationUser") as ApplicationUser ; }
    set { CallContext.SetData("ApplicationUser", value); }
}

Upvotes: 4

Views: 13734

Answers (3)

Manoochehr Dadashi
Manoochehr Dadashi

Reputation: 726

Thread-safing your second method is the best approach. This is thread-safe version of your singletone:

public sealed class SingletonType
{
    #region thread-safe singletone

    private static object _lock = new object();
    private SingletonType() { }

    public static SingletonType SingletonInstance
    {
        get
        {
            if (CallContext.GetData(key) == null)
            {
                lock (_lock)
                {
                    if (CallContext.GetData(key) == null)
                        CallContext.SetData(key, new SingletonType());
                }
            }

            return CallContext.GetData(key) as SingletonType;
        }
    }

    #endregion

    //
    //
    // SingletoneType members
    //
    //
}

NOTE : using a lock { } block is the key.

Upvotes: 0

Moo-Juice
Moo-Juice

Reputation: 38820

This was resolved during a chat session.

In essence it involves long-running tasks and a suggestion of using an external service (Web, or regular Windows Service) was decided as the best solution to the problem.

Upvotes: 1

Nikola Radosavljević
Nikola Radosavljević

Reputation: 6911

ASP.NET may migrate request between threads if it's under load. Once request is received page constructor may execute on one thread and page load on another. In this thread switch CallContext and ThreadStatic are not migrated, but luckaly HttpContext is.

This may be misleading as HttpContext is call context, but this is a little quirk in ASP.NET, probably due to cutting corners to improve performance.

You'll have to remove dependencies to CallContext and use HttpContext entire way through.

You can read more details in this terrific blog post by Piers7.

Upvotes: 4

Related Questions