ASP.NET Core 中 AsyncLocal 的安全性

Posted

技术标签:

【中文标题】ASP.NET Core 中 AsyncLocal 的安全性【英文标题】:Safety of AsyncLocal in ASP.NET Core 【发布时间】:2016-07-30 09:42:27 【问题描述】:

对于 .NET Core,AsyncLocalCallContext 的替代品。但是,尚不清楚在 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&lt;T&gt; 的工作原理。它是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进行实体映射

ASP.NET Core中的缓存[1]:如何在一个ASP.NET Core应用中使用缓存

ASP.NET Core 1.0 部署 HTTPS