ASP.NET/静态类竞争条件?

发布于 2024-07-14 04:12:04 字数 1046 浏览 5 评论 0原文

我有一个包含大量动态内容的 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 的原因)。

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

I have an ASP.NET application with a lot of dynamic content. The content is the same for all users belonging to a particular client. To reduce the number of database hits required per request, I decided to cache client-level data. I created a static class ("ClientCache") to hold the data.
The most-often used method of the class is by far "GetClientData", which brings back a ClientData object containing all stored data for a particular client. ClientData is loaded lazily, though: if the requested client data is already cached, the caller gets the cached data; otherwise, the data is fetched, added to the cache and then returned to the caller.

Eventually I started getting intermittent crashes in the the GetClientData method on the line where the ClientData object is added to the cache. Here's the method body:

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;
}

The exception text is always something like "An object with the same key already exists."
Of course, I tried to write the code so that it just wasn't possible to add a client to the cache if it already existed.

At this point, I'm suspecting that I've got a race condition and the method is being executed twice concurrently, which could explain how the code would crash. What I'm confused about, though, is how the method could be executed twice concurrently at all. As far as I know, any ASP.NET application only ever fields one request at a time (that's why we can use HttpContext.Current).

So, is this bug likely a race condition that will require putting locks in critical sections? Or am I missing a more obvious bug?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

欲拥i 2024-07-21 04:12:05

这很容易修复:

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 对象),则肯定将需要处理线程同步问题。

This is very easy to fix:

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;
  }
}

Keep in mind that whenever you mess with static classes, you have the potential for thread synchronization problems. If there's a static class-level list of some kind (in this case, _clients, the Dictionary object), there's DEFINITELY going to be thread synchronization issues to deal with.

夏末 2024-07-21 04:12:05

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

这在 ASP.NET 中根本不可能实现。

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

Your code really does assume only one thread is in the function at a time.

This just simply won't be true in ASP.NET

If you insist on doing it this way, use a static semaphore to lock the area around this class.

踏雪无痕 2024-07-21 04:12:05

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

使用 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;
}

you need thread safe & minimize lock.
see Double-checked locking (http://en.wikipedia.org/wiki/Double-checked_locking)

write simply with 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;
}
舟遥客 2024-07-21 04:12:04

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

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

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

编辑

对此答案的评论指出:-

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

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

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

If an ASP.NET application only handles one request at a time all ASP.NET sites would be in serious trouble. ASP.NET can process dozens at a time (typically 25 per CPU core).

You should use ASP.NET Cache instead of using your own dictionary to store your object. Operations on the cache are thread-safe.

Note you need to be sure that read operation on the object you store in the cache are threadsafe, unfortunately most .NET class simply state the instance members aren't thread-safe without trying to point any that may be.

Edit:

A comment to this answer states:-

Only atomic operations on the cache are thread safe. If you do something like check
if a key exists and then add it, that is NOT thread safe and can cause the item to
overwritten.

Its worth pointing out that if we feel we need to make such an operation atomic then the cache is probably not the right place for the resource.

I have quite a bit of code that does exactly as the comment describes. However the resource being stored will be the same in both places. Hence if an existing item on rare occasions gets overwritten the only the cost is that one thread unnecessarily generated a resource. The cost of this rare event is much less than the cost of trying to make the operation atomic every time an attempt to access it is made.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文