Task.Run中的ASP.NET HttpContext.Current

时间:2016-09-26 13:37:06

标签: asp.net asp.net-mvc asynchronous threadpool httpcontext

我有一个在ASP.NET MVC应用程序中使用的以下代码示例。 此代码的目的是为排队一些长时间运行的操作创建“即发即弃”请求。

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

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

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

我知道这不是在异步代码中处理HttpContext.Current的好方法,但是目前我们的实现不允许我们做其他事情。 我想了解这段代码有多危险......

问题:理论上可以在Task.Run中设置HttpContext,将上下文设置为另一个请求吗?

我想是的,但我不确定。我怎么理解: Request1由线程池中的Thread1处理,然后当Thread1绝对处理另一个请求(Request2)时,Task.Run中的代码将设置从Request1到Request2的上下文。

也许我错了,但我对ASP.NET内部的了解不允许我正确理解它。

谢谢!

2 个答案:

答案 0 :(得分:9)

让我对你说一点内幕:

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;
   }
}

所以

var context = HttpContext.Current;

等于(伪代码)

var context = CurrentThread.HttpContext;

并在Task.Run内部发生这样的事情

CurrentThread.HttpContext= context;

Task.Run将使用线程池中的线程启动新任务。所以你要告诉你的新线程" HttpContext属性"引用起始线程" HttpContext属性" - 到目前为止一切顺利(以及你的起始线程完成后你所面临的所有NullReference / Dispose异常)。问题在于你的

//Other long running code here.

您有

之类的陈述
var foo = await Bar();

一旦你等待,你的当前线程将返回到线程池,并在IO完成后从线程池中获取新线程 - 想知道它的" HttpContext属性"是正确的 ?我不知道:)很可能你会以NullReferenceException结束。

答案 1 :(得分:4)

您将遇到的问题是HttpContext将在请求完成时进行处理。由于您没有等待Task.Run的结果,因此您实际上是在处理HttpContext和它在任务中的使用之间创建竞争条件。

我很确定您的任务遇到的唯一问题是NullReferenceException或ObjectDisposedException。我没有看到任何可能意外窃取另一个请求的上下文的方法。

另外,除非你正在处理&在您的任务中记录异常,您的火灾和遗忘将会抛出,您永远不会知道它。

查看HangFire或考虑使用消息队列来处理来自单独进程的后端作业。