4

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); }
}
Community
  • 1
  • 1
Kamyar Nazeri
  • 25,786
  • 15
  • 50
  • 87
  • A HttpContext refers to a single, Http request. Queueing it up somewhere in a thread is going to cause you some issues (as you've evidently found out). Perhaps if you describe what it is you're trying to do, we can come up with a better solution? – Moo-Juice Mar 31 '12 at 16:11
  • The problem is simple, I need a single object reference in my applicatin that is instantiated with any request (Application BeginRequest maybe) and is alive throughout the request, *and* any possible thread which is started afterwards – Kamyar Nazeri Mar 31 '12 at 16:16

3 Answers3

4

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.

Nikola Radosavljević
  • 6,871
  • 32
  • 44
  • Thanks Nikola, that pretty much explains everything, however *HttpContext.Current* happens to be **null** across threads, I was thinking of using both HttpContext and CallContext, HttpContext for reular ASP.net requests from BeginRequest until the request ends, and CallContext whenever I start a new thread, what do you think? – Kamyar Nazeri Mar 31 '12 at 17:31
  • 1
    I don't see too many options. You either need to 1) pass all data you need to your method when queuing it to ThreadPool or 2) actually sending HttpContext and being careful with it. You did link to a SO question which states this "isn't thread safe". This operation is thread safe, and you will send correct HttpContext instance, but the collection it holds is not thread safe so you need to be careful how you access it. To be on safe side maybe you coud avoid assigning it to HttpContext.Current in worker thread, and use it directly, or at least cleaning it up after worker is done. – Nikola Radosavljević Mar 31 '12 at 18:59
1

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.

Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
  • The **Cache** or **Application** are general place to store data through the lifetime of the **Application**, I need a place to store *per-request* info, which could be done using *HttpContext.Current.Items*. However as I noted above, this could not be done with a multi-thread application! – Kamyar Nazeri Mar 31 '12 at 16:24
  • @KamyarNazeri, see my updated answer. Instantiate this at the beginning of the request, and destroy it at the end. It's thread-safe. – Moo-Juice Mar 31 '12 at 16:40
  • What if the request ends before another thread/threads finish working? in that case I need to keep track of each thread and somehow dispose the cache when the last thread ends! Somewhere between another request might hit the application and write wrong data to the cache! of course having different keys for different users/request is another issue that needs extra attention! – Kamyar Nazeri Mar 31 '12 at 16:48
  • @KamyarNazeri, perhaps you can expand on what it is you're actually *doing* to necessitate this? – Moo-Juice Mar 31 '12 at 16:53
  • Remember HttpContext.Current is null across threads, so session could not be the right place to store data – Kamyar Nazeri Mar 31 '12 at 17:26
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/9538/discussion-between-moo-juice-and-kamyar-nazeri) – Moo-Juice Mar 31 '12 at 17:52
0

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.

Manoochehr Dadashi
  • 715
  • 1
  • 10
  • 28
  • This is quite wrong! locking does not apply here. CallContext methods are all static and operate on the call context in the current Thread, meaning they work on data slots that are unique to each logical thread of execution and they are thread safe per se! – Kamyar Nazeri Dec 03 '15 at 07:07
  • I think null values returned from CallContext are because two threads are setting a callContext key concurrently. and [maybe] something goes wrong inside CallContext.SetData()... So locking will manage it. and there is no performance issue with it. You can test. maybe null values disappear. BTW I didn't test it myself. Locking doesn't lock a field or property or something else. it locks a block of code. I just think it's a better implementation for your second approach – Manoochehr Dadashi Dec 03 '15 at 07:51
  • If you take a look at .net source code http://referencesource.microsoft.com/#mscorlib/system/runtime/remoting/callcontext.cs,066f56e60c6fa4f5,references you'll notice that the `SetData` method actually makes use of `Thread.CurrentThread` to get a free data slot. So there's no concurrency here and no need to lock anything – Kamyar Nazeri Dec 03 '15 at 11:01
  • Ok that was a guess. But why CallContext is returning null sometimes? And what was your final solution – Manoochehr Dadashi Dec 03 '15 at 12:00
  • CallContext is not a good option in Asp.net application to begin with. Turns out, under load ASP.Net can migrate inbound requests from its IO thread pool to a queue taken up by it's worker process thread pool. i.e. your request thread may not be the same throughout asp pipeline. as Nikola Radosavljević pointed out, you can read more here: http://piers7.blogspot.fr/2005/11/threadstatic-callcontext-and_02.html – Kamyar Nazeri Dec 03 '15 at 12:42