![](/img/trans.png)
[英]AsyncLocal with ASP.NET Core Controller/ServiceProviderScope
[英]Safety of AsyncLocal in ASP.NET Core
对于 .NET Core, AsyncLocal
是CallContext
的替代品。 但是,尚不清楚在 ASP.NET Core 中使用它的“安全性”如何。
在 ASP.NET 4 (MVC 5) 及更早版本中, ASP.NET的线程敏捷模型使CallContext
不稳定。 因此,在 ASP.NET 中,实现每个请求逻辑上下文行为的唯一安全方法是使用 HttpContext.Current.Items。 在CallContext
, HttpContext.Current.Items 是用CallContext
实现的,但它是以一种对 ASP.NET 安全的方式实现的。
相比之下,在 OWIN/Katana Web API 的上下文中,线程敏捷模型不是问题。 在仔细考虑如何正确处理CallContext 之后,我能够安全地使用 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 中是否存在任何类型的老式“线AsyncLocal
性”问题会使这个中间件无法使用?
对于那些不熟悉 ASP.NET 应用程序中CallContext
问题的人,在这篇 SO 文章中,Jon Skeet引用了有关该问题的深入讨论(反过来引用了 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
构造时不会意外地构建不稳定的代码。
我只是在查看 CoreClr 的 ExecutionContext 类的源代码: https : //github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/src/mscorlib/src/System/Threading/ExecutionContext.cs
根据我对代码的理解,异步本地值是每个 ExecutionContext 实例的字段/变量。 它们不基于 ThreadLocal 或任何线程特定的持久化数据存储。
为了验证这一点,在我对线程池线程的测试中,当同一线程池线程被重用时,无法访问留在异步本地值中的实例,并且在下一个 GC 周期调用了用于清理自身的“左”实例的析构函数,这意味着该实例按预期进行了 GC。
如果AsyncLocal
在ASP.NET 经典(非核心)应用程序中“安全”,如果有人在谷歌搜索后登陆此页面(就像我所做的那样),则添加我的两美分(一些评论者一直在问这个问题,而且我看到一个已删除的答案询问一样)。
我写了一个模拟asp.net的ThreadPool行为的小测试
即使线程池重新使用现有线程, AsyncLocal
始终在请求之间被清除。 所以在这方面它是“安全的”,没有数据会泄漏到另一个线程。
但是,即使在相同的上下文中(例如在 global.asax 中运行的代码和在控制器中运行的代码之间),也可以清除AsyncLocal
。 因为 MVC 方法有时会在与非 MVC 代码分离的线程上运行,请参阅此问题,例如: asp.net mvc 4, thread changed by model binding?
使用ThreadLocal
是不安全的 b/c 它在线程池中的线程被重新使用后保留该值。 切勿在 Web 应用程序中使用 ThreadLocal。 我知道问题与 ThreadLocal 无关,我只是向考虑使用它的人添加此警告,抱歉。
在 ASP.NET MVC 5 .NET 4.7.2 下测试。
总体而言,在您无法直接访问HttpContext.Current
的情况下, AsyncLocal
似乎是替代短期缓存内容的完美选择。 不过,您最终可能会更频繁地重新计算缓存值,但这不是大问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.