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