The cross-thread usage of "HttpContext.Current" property and related things
There are four things working together to cause the behavior you are asking about:
- HttpContext is an instance object whose reference can be found in
HttpContext.Current
- Thread is also an instance object whose reference can be found in
Thread.CurrentThread
Thread.CurrentThread
is static but references a differentThread
object in every threadHttpContext.Current
actually points toThread.CurrentThread.ExecutionContext.IllogicalCallContext.HostContext
Conclusions we can draw from the above givens:
- Because
HttpContext
is an instance object and not static we need its reference to access it - Because
HttpContext.Current
actually points to a property onThread.CurrentThread
, changingThread.CurrentThread
to a different object will likely changeHttpContext.Current
- Because
Thread.CurrentThread
' changes when switching threads,HttpContext.Current
also changes when switching threads (in this caseHttpContext.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:
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.
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()
.
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.
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
.