ASP.NET HttpContext.Current在Task.Run中
我有一个在ASP.NET MVC应用程序中使用的以下代码示例。 这段代码的目的是为排队一些长时间运行的操作创建“发送和忘记”请求。ASP.NET HttpContext.Current在Task.Run中
public JsonResult SomeAction() {
HttpContext ctx = HttpContext.Current;
Task.Run(() => {
HttpContext.Current = ctx;
//Other long running code here.
});
return Json("{ 'status': 'Work Queued' }");
}
我知道这是不是在异步代码处理HttpContext.Current一个很好的方式,但目前我们的实现不会让我们到别的做一些事情。 我想明白这个代码是多少危险......
问题:是理论上可能设置的HttpContext内Task.Run,将上下文设置为完全另一个请求?
我想是的,但我不确定。我的理解如下: Request1由线程池中的Thread1处理,然后当Thread1处理绝对另一个请求(Request2)时,Task.Run中的代码将设置上下文从Request1到Request2。
也许我错了,但是我对ASP.NET内部知识的了解并不能让我正确理解它。
谢谢!
让我碰你一点内部:
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“清除”了上下文。 ..我明白你的答案了吗?谢谢! –
您将遇到的问题是,HttpContext将在请求完成时处置。由于您不是在等待Task.Run的结果,因此您本质上是在处理HttpContext和它在任务内的使用之间创建竞争条件。
我敢肯定,您的任务将遇到的唯一问题是NullReferenceException或ObjectDisposedException。我没有看到任何方式可能会意外地窃取另一个请求的上下文。另外,除非你在你的任务中处理&日志异常,否则你的火和遗忘将会抛出,你永远不会知道它。
检出HangFire或考虑使用消息队列处理来自单独进程的后端作业。
感谢您的回答。我知道竞争条件和可能的NullReference/Disposed异常。但我想从技术上理解为什么你认为这种方式不能窃取另一个请求上下文......就我所知,技术上HttpContext.Current通过线程Id来管理上下文,所以如果两个请求将接收具有相同Id的线程,则HttpContext.Current可以从两个请求中返回对同一对象的引用。 –
从HttpContext获取所需的信息,而不是传入整个HttpContext会不会更简单? (我意识到这并不能回答你的问题,但我对整个上下文的需求感到好奇)。 – vcsjones
对,这是非常正确的方式,但不幸的是,目前我无法改变它。我们的代码可以访问HttpContext.Current内部的业务逻辑,并且改变它是目前我们没有的巨大努力。 –
与您的问题无关 - 在web上下文中执行长时间运行的任务并不是一个好主意 - 服务器可以重新启动并且池中只有很多线程 - 一旦线程用完,您将停止提供请求。你有没有考虑过像HangFire或Quartz? –