【发布时间】:2016-07-30 09:42:27
【问题描述】:
对于 .NET Core,AsyncLocal 是 CallContext 的替代品。但是,尚不清楚在 ASP.NET Core 中使用它有多“安全”。
在 ASP.NET 4 (MVC 5) 及更早版本中,thread-agility model of ASP.NET made CallContext unstable。因此,在 ASP.NET 中,实现按请求逻辑上下文的行为的唯一安全方法是使用 HttpContext.Current.Items。在幕后,HttpContext.Current.Items 是用CallContext 实现的,但它是以对 ASP.NET 安全的方式完成的。
相比之下,在 OWIN/Katana Web API 的上下文中,线程敏捷模型不是问题。在careful considerations of how correctly to dispose it 之后,我能够安全地使用 CallContext。
但现在我正在处理 ASP.NET Core。我想使用以下中间件:
public class MultiTenancyMiddleware
{
private readonly RequestDelegate next;
static int random;
private static AsyncLocal<string> tenant = new AsyncLocal<string>();
//This is the new form of "CallContext".
public static AsyncLocal<string> Tenant
{
get { return tenant; }
private set { tenant = value; }
}
//This is the new verion of [ThreadStatic].
public static ThreadLocal<string> LocalTenant;
public MultiTenancyMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
//Just some garbage test value...
Tenant.Value = context.Request.Path + random++;
await next.Invoke(context);
//using (LocalTenant = new AsyncLocal<string>()) {
// Tenant.Value = context.Request.Path + random++;
// await next.Invoke(context);
//}
}
}
到目前为止,上面的代码似乎工作得很好。但至少有一个危险信号。过去,确保CallContext 被视为必须在每次调用后释放的资源非常重要。
现在我发现没有不言而喻的“清理”AsyncLocal 的方法。
我包含了代码,注释掉了,展示了ThreadLocal<T> 的工作原理。它是IDisposable,因此它具有明显的清理机制。相反,AsyncLocal 不是IDisposable。这令人不安。
这是因为AsyncLocal 尚未处于发布候选状态吗?还是因为真的不再需要进行清理了?
即使AsyncLocal 在我上面的示例中使用得当,ASP.NET Core 中是否存在任何类型的老式“线程敏捷性”问题会使该中间件无法使用?
特别说明
对于那些不熟悉CallContext 在 ASP.NET 应用程序中的问题的人,this SO post, Jon Skeet 中引用了 in-depth discussion about the problem(反过来又引用了 commentary from Scott Hanselman)。这个“问题”不是错误 - 它只是一个必须仔细考虑的情况。
此外,我可以亲自证明这种不幸的行为。当我构建 ASP.NET 应用程序时,我通常将负载测试作为我的自动化测试基础结构的一部分。在负载测试期间,我可以看到 CallContext 变得不稳定(可能有 2% 到 4% 的请求显示 CallContext 已损坏。我还看到 Web API GET 具有稳定的 CallContext 行为的情况,但是POST 操作都是不稳定的。实现完全稳定的唯一方法是依赖 HttpContext.Current.Items。
但是,对于 ASP.NET Core,我不能依赖 HttpContext.Items...没有这样的静态访问点。我还不能为我正在修补的 .NET Core 应用程序创建负载测试,这也是我没有为自己回答这个问题的部分原因。 :)
再次重申:请理解我所讨论的“不稳定”和“问题”根本不是错误。 CallContext 没有任何缺陷。这个问题只是 ASP.NET 使用的线程调度模型的结果。解决方案只是知道问题的存在,并进行相应的编码(例如,在 ASP.NET 应用程序中使用
HttpContext.Current.Items而不是CallContext)。
我对这个问题的目标是了解这种动态如何在 ASP.NET Core 中应用(或不应用),这样我就不会在使用新的 AsyncLocal 构造时意外构建不稳定的代码。
【问题讨论】:
-
您看到 modern (4.5) 逻辑调用上下文失败了吗?
-
@StephenCleary 我的大多数显示不稳定性的测试数据来自 MVC 3、MVC 4 和 Web API 2.0-2.2。使用 HttpContext.Current.Items 而不是
CallContext总是可以解决问题。也许 MVC 5 “修复”了这个问题,而我根本不知道;我敢打赌“不会”。问题从来不在于CallContext本身被破坏,而是 ASP.NET 使用的线程调度模型不兼容(除非 ASP.NET 通过 HttpContext.Current.Items 直接控制)。 -
澄清一下,您使用的是 logical 调用上下文?
-
是的,Richter 证明的“逻辑”。此外,.net 4.5 之前缺少“写入时复制”不是问题,因为在我测试的这些应用程序中,逻辑调用上下文被视为不可变的(每个请求设置一次,此后未触及)。其他不相关的琐事:如果它在除
DispatchOperationextension points 之外的任何地方建立,它在 WCF 中也是不稳定的。 -
我发现虽然在 ASP.NET AsyncLocal 不是很可靠(在 global.asax 和控制器之间切换时可能会丢失)但在 ASP.NTE Core 中是可靠的。事实上,ASP.NET Core 中的 HttpContext.Current 是通过 AsyncLocal var 完成的
标签: c# asp.net-core .net-core