ASP.NET HttpContext.Current inside Task.Run

2019-04-06 04:14发布

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!

2条回答
forever°为你锁心
2楼-- · 2019-04-06 04:51

The issue you will run into here is that the HttpContext will dispose when the request is complete. Since you aren't awaiting the result of the Task.Run, you are essentially creating a race condition between the disposal of the HttpContext and it's usage within the task.

I'm pretty sure that the only issue your task will run into is a NullReferenceException or an ObjectDisposedException. I don't see any way where you could accidentally steal another request's context.

Also, unless you are handling & logging exceptions within your task, your fire and forget will throw and you'll never know about it.

Check out HangFire or consider using a message queue for processing backend jobs from a separate process.

查看更多
地球回转人心会变
3楼-- · 2019-04-06 04:58

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.

查看更多
登录 后发表回答