【问题标题】:ASP.NET/Static class Race Condition?ASP.NET/静态类竞争条件?
【发布时间】:2010-10-09 02:19:02
【问题描述】:

我有一个包含大量动态内容的 ASP.NET 应用程序。属于特定客户端的所有用户的内容都是相同的。为了减少每个请求所需的数据库命中次数,我决定缓存客户端级数据。我创建了一个静态类(“ClientCache”)来保存数据。
迄今为止,该类最常用的方法是“GetClientData”,它返回一个包含特定客户端所有存储数据的 ClientData 对象。但是,ClientData 是延迟加载的:如果请求的客户端数据已经缓存,则调用者获取缓存的数据;否则,获取数据,添加到缓存中,然后返回给调用者。

最终,我在将 ClientData 对象添加到缓存的那一行的 GetClientData 方法中开始出现间歇性崩溃。这是方法体:

public static ClientData GetClientData(Guid fk_client)
{
    if (_clients == null)
        _clients = new Dictionary<Guid, ClientData>();

    ClientData client;
    if (_clients.ContainsKey(fk_client))
    {
        client = _clients[fk_client];
    }
    else
    {
        client = new ClientData(fk_client);
        _clients.Add(fk_client, client);
    }
    return client;
}

异常文本总是类似于“已存在具有相同键的对象”。 当然,我尝试编写代码,以便在缓存已经存在的情况下无法将客户端添加到缓存中。

此时,我怀疑我遇到了竞争条件,并且该方法同时执行了两次,这可以解释代码是如何崩溃的。但是,我感到困惑的是,该方法如何可以同时执行两次。据我所知,任何 ASP.NET 应用程序一次只能处理一个请求(这就是我们可以使用 HttpContext.Current 的原因)。

那么,这个错误是否可能是需要在关键部分加锁的竞争条件?还是我错过了一个更明显的错误?

【问题讨论】:

    标签: asp.net static race-condition static-members


    【解决方案1】:

    你需要线程安全和最小化锁。
    请参阅双重检查锁定 (http://en.wikipedia.org/wiki/Double-checked_locking)

    用 TryGetValue 简单地编写。

    
    public static object lockClientsSingleton = new object();
    
    public static ClientData GetClientData(Guid fk_client)
    {
        if (_clients == null) {
            lock( lockClientsSingleton ) {
                if( _clients==null ) {
                    _clients = new Dictionary``();
                }
            }
        }
        ClientData client;
        if( !_clients.TryGetValue( fk_client, out client ) )
        {
            lock(_clients) 
            {
                if( !_clients.TryGetValue( fk_client, out client ) ) 
                {
                    client = new ClientData(fk_client)
                    _clients.Add( fk_client, client );
                }
            }
        }
        return client;
    }
    

    【讨论】:

      【解决方案2】:

      如果一个 ASP.NET 应用程序一次只处理一个请求,那么所有 ASP.NET 站点都会遇到严重的问题。 ASP.NET 一次可以处理数十个(通常每个 CPU 内核 25 个)。

      您应该使用 ASP.NET 缓存而不是使用您自己的字典来存储您的对象。对缓存的操作是线程安全的。

      请注意,您需要确保对存储在缓存中的对象的读取操作是线程安全的,不幸的是,大多数 .NET 类只是简单地声明实例成员不是线程安全的,而没有尝试指向任何可能的对象。

      编辑

      对此答案的评论指出:-

      只有缓存上的原子操作是线程安全的。如果您执行检查
      之类的操作 如果一个键存在然后添加它,这不是线程安全的,并且可能导致项目
      覆盖。

      值得指出的是,如果我们觉得需要使这样的操作原子化,那么缓存可能不是资源的正确位置。

      我有相当多的代码与评论描述的完全一样。但是,在这两个地方存储的资源将是相同的。因此,如果一个现有项目在极少数情况下被覆盖,唯一的代价是一个线程不必要地生成了一个资源。这种罕见事件的成本远低于每次尝试访问它时尝试使操作原子化的成本。

      【讨论】:

      • 感谢您的解释。我知道 ASP.NET 输出缓存,但不知道 Cache 对象。缓存 API 将更干净地解决我的问题。
      • 只有缓存上的原子操作是线程安全的。如果您执行诸如检查密钥是否存在然后添加它之类的操作,这不是线程安全的,并且可能导致项目被覆盖。
      【解决方案3】:

      这很容易解决:

      private _clientsLock = new Object();
      
      public static ClientData GetClientData(Guid fk_client)
      {
        if (_clients == null)
          lock (_clientsLock)
            // Check again because another thread could have created a new 
            // dictionary in-between the lock and this check
            if (_clients == null) 
              _clients = new Dictionary<Guid, ClientData>();
      
        if (_clients.ContainsKey(fk_client))
          // Don't need a lock here UNLESS there are also deletes. If there are
          // deletes, then a lock like the one below (in the else) is necessary
          return _clients[fk_client];
        else
        {
          ClientData client = new ClientData(fk_client);
      
          lock (_clientsLock)
            // Again, check again because another thread could have added this
            // this ClientData between the last ContainsKey check and this add
            if (!clients.ContainsKey(fk_client))
             _clients.Add(fk_client, client);
      
          return client;
        }
      }
      

      请记住,每当您使用静态类时,都有可能出现线程同步问题。如果存在某种静态类级列表(在本例中为 _clients,Dictionary 对象),则肯定将要处理线程同步问题。

      【讨论】:

      • 感谢您的详细解释。正好赶时间,所以我继续锁定解决方案,同时等待我的知识空白在这里被填补。我的代码和你的代码之间唯一真正的区别是我必须锁定整个类,因为该类是静态的。
      • 锁定类会导致严重的资源争用;你不想那样做。
      【解决方案4】:

      您的代码确实假设一次只有一个线程在函数中。

      这在 ASP.NET 中根本不适用

      如果您坚持这样做,请使用静态信号量来锁定此类周围的区域。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-23
        • 2017-07-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多