我有一个包含大量动态内容的ASP.NET应用程序。属于特定客户端的所有用户的内容相同。为了减少每个请求所需的数据库命中数,我决定缓存客户端级数据。我创建了一个静态类(“ClientCache”)来保存数据 该类最常用的方法是到目前为止“GetClientData”,它返回一个ClientData对象,该对象包含特定客户端的所有存储数据。但是,懒惰地加载ClientData:如果请求的客户端数据已经被缓存,则调用者获取缓存的数据;否则,将获取数据,将其添加到缓存中,然后返回给调用者。
最终,我开始在ClientClData对象添加到缓存的行上的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的原因)。
那么,这个bug是否可能是竞争条件,需要在关键部分放锁?或者我错过了一个更明显的错误?
答案 0 :(得分:3)
如果ASP.NET应用程序一次只处理一个请求,则所有ASP.NET站点都会遇到严重问题。 ASP.NET可以一次处理几十个(通常每个CPU核心25个)。
您应该使用ASP.NET Cache而不是使用自己的字典来存储对象。缓存上的操作是线程安全的。
请注意,您需要确保对存储在缓存中的对象的读取操作是线程安全的,不幸的是,大多数.NET类只是声明实例成员不是线程安全的,而不试图指向可能的任何内容。
修改强>:
对此答案的评论指出: -
只有缓存上的原子操作是线程安全的。如果您执行类似检查的事项 如果存在一个密钥然后添加它,那就不是线程安全的,并且可能导致项目为
覆盖。
值得指出的是,如果我们觉得我们需要使这样的操作成为原子,那么缓存可能不是资源的正确位置。
我有很多代码完全按照评论所描述的那样完成。但是,存储的资源在两个地方都是相同的。因此,如果在极少数情况下现有项被覆盖,则唯一的成本是一个线程不必要地生成资源。这种罕见事件的成本远低于每次尝试访问它时尝试使操作成为原子的成本。
答案 1 :(得分:2)
这很容易解决:
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
对象),则 DEFINITELY 将成为要处理的线程同步问题。
答案 2 :(得分:0)
您的代码确实假设一次只有一个线程在函数中。
这在ASP.NET
中根本不会成立如果您坚持这样做,请使用静态信号量来锁定此类周围的区域。
答案 3 :(得分:0)
你需要线程安全&amp;最小化锁定
请参阅双重检查锁定(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;
}