【问题标题】:ASP.NET HttpContext.Current inside Task.RunTask.Run 中的 ASP.NET HttpContext.Current
【发布时间】:2017-02-03 21:21:34
【问题描述】:

我有一个在 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 内部原理的了解无法让我正确理解它。

谢谢!

【问题讨论】:

  • 从 HttpContext 中获取您需要的信息,而不是传入整个 HttpContext 不是更简单吗? (我意识到这并不能回答你的问题,但我很好奇整个上下文的需要)。
  • 是的,这是非常正确的方法,但不幸的是,目前我无法更改它。我们的代码可以访问 HttpContext.Current 深入的业务逻辑,而改变它是一项巨大的努力,目前我们还没有。
  • 与您的问题无关 - 在 Web 上下文中长时间运行任务不是一个好主意 - 服务器可以重新启动并且池中只有这么多线程 - 一旦你用完线程你'将停止处理请求。你有考虑过像 HangFire 或 Quartz 这样的东西吗?
  • @OndrejSvejdar,你是对的,但这是我们目前拥有的,现在无法改变架构。
  • 你有没有机会在 //Other long running code 中使用“await”。 ?

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


【解决方案1】:

让我来了解一下你的内部情况:

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 结束。

【讨论】:

  • 感谢您的详细解答!从您所写的内容来看,我知道从技术上讲它可能会发生(Request2 将获得 Request1 的上下文),但是我很可能会获得空引用并处理异常,因为实际上 Request1 已经完成并且 ASP.NET “清除”了它的上下文。 ..我理解你的答案吗?谢谢!
【解决方案2】:

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

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

此外,除非您在任务中处理和记录异常,否则您的“一劳永逸”会抛出,您永远不会知道。

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

【讨论】:

  • 感谢您的回答。我知道竞态条件和可能的 NullReference / Disposed 异常。但我想从技术上理解为什么你认为这种方式不能窃取另一个请求上下文......据我所知,从技术上讲 HttpContext.Current 通过线程 ID 管理上下文,所以如果两个请求将接收具有相同 ID 的线程,则 HttpContext.Current 可以从两个请求返回对同一对象的引用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-09
  • 2020-08-21
  • 2014-05-29
  • 1970-01-01
  • 2015-07-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多