Alex Dn Alex Dn - 2 months ago 51
ASP.NET (C#) Question

ASP.NET HttpContext.Current inside Task.Run

I have a following code example that is used in ASP.NET MVC application.
The purpose of this code is to create "fire and forget" request for queuing some long running operation.

public JsonResult SomeAction() {
HttpContext ctx = HttpContext.Current;

Task.Run(() => {
HttpContext.Current = ctx;
//Other long running code here.
});

return Json("{ 'status': 'Work Queued' }");
}


I know this is not a good way for handling HttpContext.Current in asynchronous code, but currently our implementation not allows us to do something else.
I would like to understand how much this code is dangerous...

The question: Is it theoretically possible that setting the HttpContext inside Task.Run, will set the context to totally another request?

I think yes, but I'm not sure. How I understand it:
Request1 is handled with Thread1 from thread pool, then while Thread1 is handling absolutelly another request (Request2), the code inside Task.Run will set context from Request1 to Request2.

Maybe I am wrong, but my knowledge of ASP.NET internals not allows me to understand it correctly.

Thanks!

Answer

Let me bump a little internals on you:

public static HttpContext Current
{
    get { return ContextBase.Current as HttpContext; }
    set { ContextBase.Current = value; }
}

internal class ContextBase
{
    internal static object Current
    {
        get { return CallContext.HostContext; }
        set { CallContext.HostContext = value; }
    }
}

public static object HostContext
{
    get 
    {
        var executionContextReader = Thread.CurrentThread.GetExecutionContextReader();
        object hostContext = executionContextReader.IllogicalCallContext.HostContext;
        if (hostContext == null)
        {
            hostContext = executionContextReader.LogicalCallContext.HostContext;
        }
        return hostContext;
   }
   set
   {
        var mutableExecutionContext = Thread.CurrentThread.GetMutableExecutionContext();
        if (value is ILogicalThreadAffinative)
        {
            mutableExecutionContext.IllogicalCallContext.HostContext = null;
            mutableExecutionContext.LogicalCallContext.HostContext = value;
            return;
        }
        mutableExecutionContext.IllogicalCallContext.HostContext = value;
        mutableExecutionContext.LogicalCallContext.HostContext = null;
   }
}

So

var context = HttpContext.Current;

is equal to (pseudocode)

var context = CurrentThread.HttpContext;

and inside your Task.Run something like this happens

CurrentThread.HttpContext= context;

Task.Run will start new task with thread from thread pool. So you're telling that your new thread "HttpContext property" is reference to starter thread "HttpContext property" - so far so good (well with all the NullReference/Dispose exceptions you'll be facing after your starter thread finishes). Problem is if inside your

//Other long running code here.

You have statement like

var foo = await Bar();

Once you hit await, your current thread is returned to thread pool, and after IO finishes you grab new thread from thread pool - wonder what its "HttpContext property" is, right ? I don't know :) Most probably you'll end with NullReferenceException.