ASP.NET核心中的AsyncLocal的安全性

时间:2016-04-09 00:11:39

标签: c# asp.net-core .net-core

对于.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<T>的工作原理。它是IDisposable,因此它有一个明显的清理机制。相反,AsyncLocal不是IDisposable。这令人不安。

这是因为AsyncLocal尚未处于发布候选条件吗?或者这是因为真的不再需要进行清理了吗?

即使在上面的示例中正确使用了AsyncLocal,ASP.NET Core中是否存在任何种类的旧学校“线程敏捷性”问题,这些问题会使这个中间件无法工作?

特别提示

对于那些不熟悉ASP.NET应用程序中CallContext个问题的人,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构造时不会意外地构建不稳定的代码。

2 个答案:

答案 0 :(得分:13)

我只是查看CoreClr的ExecutionContext类的源代码: https://github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/src/mscorlib/src/System/Threading/ExecutionContext.cs

根据我对代码的理解,异步本地值是每个ExecutionContext实例的字段/变量。它们不基于ThreadLocal或任何特定于线程的持久数据存储。

为了验证这一点,在我使用线程池线程进行的测试中,当重用相同的线程池线程时,无法访问异步本地值中的实例,并且&#34; left&#34;实例的清理自身的析构函数在下一个GC循环中调用,这意味着实例按预期进行了GC。

答案 1 :(得分:4)

如果有人在 ASP.NET classic (非Core)应用程序中AsyncLocal是“安全的”,则谷歌搜索后,如果有人登陆此页面(就像我那样),则加我两分钱(一些评论者一直在问这个问题,而且我也看到一个删除的答案,询问的问题差不多。

我写了一个小测试来模拟asp.net的ThreadPool行为

  1. AsyncLocal 总是在请求之间清除,即使线程池重新使用现有线程也是如此。因此在这方面是“安全的”,不会有数据泄漏到另一个线程。

  2. 但是,即使在相同的上下文中(例如,在global.asax中运行的代码与控制器中运行的代码之间),也可以清除AsyncLocal。由于MVC方法有时会与非MVC代码在单独的线程上运行,因此请参见以下问题:asp.net mvc 4, thread changed by model binding?

  3. 使用ThreadLocal是不安全的b / c,它会在重新使用线程池中的线程后保留该值。切勿在Web应用程序中使用ThreadLocal。我知道问题不在于ThreadLocal,我只是向所有考虑使用它的人添加此警告,对不起。

在ASP.NET MVC 5 .NET 4.7.2下进行了测试。

总体而言,在您无法直接访问AsyncLocal的情况下,HttpContext.Current似乎是ls-files中短时间缓存的完美替代。尽管您可能最终会重新计算缓存的值,但是这并不是一个大问题。