ASP.NET Core 中 AsyncLocal 的安全性
Posted
技术标签:
【中文标题】ASP.NET Core 中 AsyncLocal 的安全性【英文标题】:Safety of AsyncLocal in ASP.NET Core 【发布时间】: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 之前缺少“写入时复制”不是问题,因为在我测试的这些应用程序中,逻辑调用上下文被视为不可变的(每个请求设置一次,此后未触及)。其他不相关的琐事:如果它在除DispatchOperation
extension points 之外的任何地方建立,它在 WCF 中也是不稳定的。
我发现虽然在 ASP.NET AsyncLocal 不是很可靠(在 global.asax 和控制器之间切换时可能会丢失)但在 ASP.NTE Core 中是可靠的。事实上,ASP.NET Core 中的 HttpContext.Current 是通过 AsyncLocal var 完成的
【参考方案1】:
我只是在查看 CoreClr 的 ExecutionContext 类的源代码: https://github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/src/mscorlib/src/System/Threading/ExecutionContext.cs
根据我对代码的理解,异步本地值是每个 ExecutionContext 实例的字段/变量。它们不基于 ThreadLocal 或任何线程特定的持久数据存储。
为了验证这一点,在我对线程池线程的测试中,当同一个线程池线程被重用时,无法访问留在异步本地值中的实例,并且在下一次 GC 上调用了“左”实例的用于清理自身的析构函数循环,表示实例按预期被 GC。
【讨论】:
@brient-aras 好的,但是经典 .net 4.6+ 中的 AsyncLocal 怎么样?它是否也被认为是安全的? 是的,它也应该是安全的。 .net 4.6+ 和 .net core 中 AsyncLocal 的实现一开始略有不同,但最终应该是相同的。 @TeddyMa,它应该是安全的 .NET 4.6+,但不幸的是它不是。出于此处所述的原因 - github.com/MiniProfiler/dotnet/issues/173【参考方案2】:如果在 ASP.NET 经典(非核心)应用程序中 AsyncLocal
是“安全的”(一些评论者已经问这个,我也看到一个已删除的答案问同样的问题)。
我写了一个模拟asp.net的ThreadPool行为的小测试
AsyncLocal
总是在请求之间清除,即使线程池重新使用现有线程。所以在这方面它是“安全的”,不会有数据泄露给另一个线程。
然而,AsyncLocal
甚至可以在相同的上下文中被清除(例如在 global.asax 中运行的代码和在控制器中运行的代码之间)。因为 MVC 方法有时会在与非 MVC 代码不同的线程上运行,请参阅此问题,例如:asp.net mvc 4, thread changed by model binding?
使用ThreadLocal
是不安全的,因为它会在重新使用线程池中的线程后保留该值。切勿在 Web 应用程序中使用 ThreadLocal。我知道问题与 ThreadLocal 无关,我只是将此警告添加给考虑使用它的人,对不起。
在 ASP.NET MVC 5 .NET 4.7.2 下测试。
总体而言,AsyncLocal
在您无法直接访问后者的情况下,似乎是HttpContext.Current
中短时缓存内容的完美替代品。不过,您最终可能会更频繁地重新计算缓存值,但这不是什么大问题。
【讨论】:
以上是关于ASP.NET Core 中 AsyncLocal 的安全性的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET Core 配置 MVC - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core 动作结果 - ASP.NET Core 基础教程 - 简单教程,简单编程
asp.net core1.x/asp.net core2.0中如何加载多个配置文件
ASP.NET Core Web 应用程序系列- 在ASP.NET Core中使用AutoMapper进行实体映射