ASP.NET HttpContext.Current 里面 Task.Run

ASP.NET HttpContext.Current inside Task.Run

我在 ASP.NET MVC 应用程序中使用了以下代码示例。 这段代码的目的是创建 "fire and forget" 请求排队一些长 运行 操作。

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 内部结构的了解让我无法正确理解它。

谢谢!

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

我很确定您的任务 运行 遇到的唯一问题是 NullReferenceException 或 ObjectDisposedException。我看不出有什么方法可以不小心窃取另一个请求的上下文。

此外,除非您在任务中处理和记录异常,否则您的即发即弃将会抛出并且您永远不会知道。

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

让我向您介绍一下内部结构:

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 property" 是对启动线程 "HttpContext property" 的引用 - 到目前为止一切顺利(还有所有 NullReference/Dispose 启动线程完成后你将面临的异常).问题是如果在你的

//Other long running code here.

你有这样的陈述

var foo = await Bar();

一旦你点击 await,你当前的线程就会返回到线程池,IO 完成后你会从线程池中获取新线程 - 想知道它的 "HttpContext property" 是什么,对吧?我不知道 :) 您很可能会以 NullReferenceException 结束。