43

I read from < Essential ASP.NET with Examples in C# > the following statement:

Another useful property to know about is the static Current property of the HttpContext class. This property always points to the current instance of the HttpContext class for the request being serviced. This can be convenient if you are writing helper classes that will be used from pages or other pipeline classes and may need to access the context for whatever reason. By using the static Current property to retrieve the context, you can avoid passing a reference to it to helper classes. For example, the class shown in Listing 4-1 uses the Current property of the context to access the QueryString and print something to the current response buffer. Note that for this static property to be correctly initialized, the caller must be executing on the original request thread, so if you have spawned additional threads to perform work during a request, you must take care to provide access to the context class yourself.

I am wondering about the root cause of the bold part, and one thing leads to another, here is my thoughts:

We know that a process can have multiple threads. Each of these threads have their own stacks, respectively. These threads also have access to a shared memory area, the heap.

The stack then, as I understand it, is kind of where all the context for that thread is stored. For a thread to access something in the heap it must use a pointer, and the pointer is stored on its stack.

So when we make some cross-thread calls, we must make sure that all the necessary context info is passed from the caller thread's stack to the callee thread's stack.

But I am not quite sure if I made any mistake.

Any comments will be deeply appreciated.

Thanks.

ADD

Here the stack is limited to user stack.

Mark Rucker
  • 6,952
  • 4
  • 39
  • 65
smwikipedia
  • 61,609
  • 92
  • 309
  • 482

3 Answers3

95

There are four things working together to cause the behavior you are asking about:

  1. HttpContext is an instance object whose reference can be found in HttpContext.Current
  2. Thread is also an instance object whose reference can be found in Thread.CurrentThread
  3. Thread.CurrentThread is static but references a different Thread object in every thread
  4. HttpContext.Current actually points to Thread.CurrentThread.ExecutionContext.IllogicalCallContext.HostContext

Conclusions we can draw from the above givens:

  1. Because HttpContext is an instance object and not static we need its reference to access it
  2. Because HttpContext.Current actually points to a property on Thread.CurrentThread, changing Thread.CurrentThread to a different object will likely change HttpContext.Current
  3. Because Thread.CurrentThread' changes when switching threads, HttpContext.Current also changes when switching threads (in this case HttpContext.Current becomes null).

Bringing this all together, what causes HttpContext.Current to not work in a new Thread? The Thread.CurrentThread reference change, which happens when switching threads, changes the HttpContext.Current reference, which prevents us from getting to the HttpContext instance we want.

To reiterate, the only magic thing going on here is Thread.CurrentThread referencing a different object in every Thread. HttpContext works just like any other instance object. Since threads in the same AppDomain can reference the same objects, all we have to do is pass a reference for HttpContext to our new thread. There is no context info to load or anything like that. (there are some fairly serious potential gotchas with passing around HttpContext to other threads but nothing to prevent you from doing it).

A few final side notes I came across while researching:

  1. In some cases a Thread's ExecutionContext is 'flowed' (copied) from one Thread to another. Why then is HttpContext not 'flowed' to our new Thread? Because HttpContext doesn't implement the ILogicalThreadAffinative interface. A class stored in the ExecutionContext is only flowed if it implements ILogicalThreadAffinative.

  2. How does ASP.NET move HttpContext from Thread to Thread (Thread-Agility) if it isn't flowed? I'm not entirely sure, but it looks like it might pass it in HttpApplication.OnThreadEnter().

Mark Rucker
  • 6,952
  • 4
  • 39
  • 65
  • 3
    I guess the OP is not coming back to award the bounty since he hasn't logged in since the 29th so I think you should have the auto-award for this very thorough answer. +1 to put you on top. :) – Tudor Sep 03 '12 at 16:13
  • With regards to point 2, I believe this is due to the posting of messages on the ASP.NET SynchronizationContext. This is why if you don't use the SynchronizationContext to call a callback, HttpContext.Current is null and otherwise it isn't. – Marcus Nov 17 '12 at 16:48
  • 2
    A digression, but I love that you call it IllogicalContext (it's one L too many, with comic effect). – The Dag Oct 27 '15 at 12:03
  • Is there any workaround or pattern for accessing HttpContext from another thread? – AlexVPerl Feb 02 '16 at 22:19
  • Hi Axel, as long as you have a reference to the HttpContext you should be able to access it from any thread. I think the biggest danger with this is the ResponseStream being disposed when the original request thread completes. If your reason for doing this is a long running task I suggest you look into other patterns like the ASP.NET Asynchronous Controller. – Mark Rucker Feb 03 '16 at 15:58
  • @MarkRucker How do you send a reference of `HttpContext`? As a parameter, [it throws exception](http://stackoverflow.com/q/1723919/2404470) – Zameer Ansari Feb 18 '16 at 10:49
  • 1
    @student I guess it depends on where and how you are trying to pass it. It sounds like .net is attempting to marshal your context. My guess at the cause for that is passing the reference to an HTTP endpoint in your asp.net application. Suffice it to say, if we were only talking about Web API's or Multi-threaded code things would be complicated. Combining the two, the situation becomes even more complex. Unfortunately, that means you may have to do a moderate amount of background research before you find an answer which you can leverage consistently. – Mark Rucker Feb 18 '16 at 15:27
  • @MarkRucker For the time being, I'm settling with [normal strings](http://stackoverflow.com/questions/35483658/httpcontext-is-null-in-a-method-called-inside-parallel-for#comment58663782_35483879) however I don't know when the situation will change. Do you think it's logical? – Zameer Ansari Feb 18 '16 at 15:39
10

I think I've found a suitable explanation here: http://odetocode.com/articles/112.aspx

To summarize, the code-behind of HttpContext.Current looks like this:

public static HttpContext get_Current()
{ 
    return (CallContext.GetData("HtCt") as HttpContext); 
}

with CallContext functioning as thread-local storage (i.e. each thread will see a different copy of the data and cannot access the copies seen by other threads). Hence, once the current context is initialized on one thread, subsequent accesses by other threads will result in a NullReferenceException, since the property is thread-local to the initial thread.

So yes, your initial explanation was close, in the sense that it is a problem of data being visible only to a single thread.

Tudor
  • 61,523
  • 12
  • 102
  • 142
  • @Mark Rucker: I meant visible in an abstract sense to show the OP that he was right on some level i.e. only one thread can access the original instance. – Tudor Sep 01 '12 at 19:55
1

The backing field of Current is marked as ThreadStatic (I assume), hence it wont be available/initialized in user-created threads.

What it comes down to is, that you should capture the instance of HttpContext.Current in the request thread and then use that instance in your threads, instead of referring to HttpContext.Current.

leppie
  • 115,091
  • 17
  • 196
  • 297
  • 1
    The backing field of Current isn't ThreadStatic, even though it behaves a little like it is. I had assumed the same thing until I looked further into it in order to answer this question. – Mark Rucker Sep 01 '12 at 03:53
  • 1
    As already pointed out, your assumption is incorrect. Furthermore, using the context in other threads is *dangerous* since it's lifetime is scoped to the request. It may be disposed of or otherwise messed with while the other thread(s) still rely on it. Far better to create your own type for the data you need and pass it to the code that needs it. – The Dag Oct 27 '15 at 12:07