C# 缓存和线程安全
我通过System.Web.caching.Cache-Class在ASP.NET网站中缓存数据,因为检索数据的成本非常高,而且当我们的内容人员在后端更改数据时,数据只会偶尔更改一次 所以我在Application_Start中创建数据并将其存储在缓存中,过期时间为1天 当访问数据(发生在网站的许多页面上)时,我现在在一个静态CachedData类中有如下内容:C# 缓存和线程安全,c#,asp.net,caching,thread-safety,C#,Asp.net,Caching,Thread Safety,我通过System.Web.caching.Cache-Class在ASP.NET网站中缓存数据,因为检索数据的成本非常高,而且当我们的内容人员在后端更改数据时,数据只会偶尔更改一次 所以我在Application_Start中创建数据并将其存储在缓存中,过期时间为1天 当访问数据(发生在网站的许多页面上)时,我现在在一个静态CachedData类中有如下内容: public static List<Kategorie> GetKategorieTitelListe(Cache ap
public static List<Kategorie> GetKategorieTitelListe(Cache appCache)
{
// get Data out of Cache
List<Kategorie> katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;
// Cache expired, retrieve and store again
if (katList == null)
{
katList = DataTools.BuildKategorienTitelListe();
appCache.Insert(CachedData.NaviDataKey, katList, null, DateTime.Now.AddDays(1d), Cache.NoSlidingExpiration);
}
return katList;
}
public静态列表GetKategorieTitelListe(Cache-appCache)
{
//从缓存中获取数据
List katList=appCache[CachedData.NaviDataKey]作为列表;
//缓存已过期,请重新检索和存储
if(katList==null)
{
katList=DataTools.buildkategorientitellite();
Insert(CachedData.NaviDataKey,katList,null,DateTime.Now.AddDays(1d),Cache.NoSlidingExpiration);
}
返回列表;
}
我看到这段代码的问题是它不是线程安全的。
如果两个用户同时打开其中两个页面,而缓存刚刚用完,则存在数据被多次检索的风险
但是如果我锁定方法体,我将遇到性能问题,因为一次只有一个用户可以获取数据列表
有没有一个简单的方法来防止这种情况?对于这样的情况,什么是最佳实践?您是对的,您的代码不是线程安全的
// this must be class level variable!!!
private static readonly object locker = new object();
public static List<Kategorie> GetKategorieTitelListe(Cache appCache)
{
// get Data out of Cache
List<Kategorie> katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;
// Cache expired, retrieve and store again
if (katList == null)
{
lock (locker)
{
katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;
if (katlist == null) // make sure that waiting thread is not executing second time
{
katList = DataTools.BuildKategorienTitelListe();
appCache.Insert(CachedData.NaviDataKey, katList, null, DateTime.Now.AddDays(1d), Cache.NoSlidingExpiration);
}
}
}
return katList;
}
//这必须是类级变量!!!
私有静态只读对象锁定器=新对象();
公共静态列表GetKategorieTitelListe(缓存appCache)
{
//从缓存中获取数据
List katList=appCache[CachedData.NaviDataKey]作为列表;
//缓存已过期,请重新检索和存储
if(katList==null)
{
锁(储物柜)
{
katList=appCache[CachedData.NaviDataKey]作为列表;
if(katlist==null)//确保等待的线程没有第二次执行
{
katList=DataTools.buildkategorientitellite();
Insert(CachedData.NaviDataKey,katList,null,DateTime.Now.AddDays(1d),Cache.NoSlidingExpiration);
}
}
}
返回列表;
}
MSDN
声明ASP.NET缓存类是线程安全的——这意味着它们的内容可以由AppDomain中的任何线程自由访问(例如,读/写将是原子的)
请记住,随着缓存大小的增加,同步的成本也会增加。你可能想看看这篇文章
通过添加要锁定的私有对象,您应该能够安全地运行您的方法,以便其他线程不会干扰
private static readonly myLockObject = new object();
public static List<Kategorie> GetKategorieTitelListe(Cache appCache)
{
// get Data out of Cache
List<Kategorie> katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;
lock (myLockObject)
{
// Cache expired, retrieve and store again
if (katList == null)
{
katList = DataTools.BuildKategorienTitelListe();
appCache.Insert(CachedData.NaviDataKey, katList, null, DateTime.Now.AddDays(1d), Cache.NoSlidingExpiration);
}
return katList;
}
}
private static readonly myLockObject=new object();
公共静态列表GetKategorieTitelListe(缓存appCache)
{
//从缓存中获取数据
List katList=appCache[CachedData.NaviDataKey]作为列表;
锁定(myLockObject)
{
//缓存已过期,请重新检索和存储
if(katList==null)
{
katList=DataTools.buildkategorientitellite();
Insert(CachedData.NaviDataKey,katList,null,DateTime.Now.AddDays(1d),Cache.NoSlidingExpiration);
}
返回列表;
}
}
除了锁定,我看不到其他解决方案
private static readonly object _locker = new object ();
public static List<Kategorie> GetKategorieTitelListe(Cache appCache)
{
List<Kategorie> katList;
lock (_locker)
{
// get Data out of Cache
katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;
// Cache expired, retrieve and store again
if (katList == null)
{
katList = DataTools.BuildKategorienTitelListe();
appCache.Insert(CachedData.NaviDataKey, katList, null, DateTime.Now.AddDays(1d), Cache.NoSlidingExpiration);
}
}
return katList;
}
私有静态只读对象_locker=new object();
公共静态列表GetKategorieTitelListe(缓存appCache)
{
名单;
锁(储物柜)
{
//从缓存中获取数据
katList=appCache[CachedData.NaviDataKey]作为列表;
//缓存已过期,请重新检索和存储
if(katList==null)
{
katList=DataTools.buildkategorientitellite();
Insert(CachedData.NaviDataKey,katList,null,DateTime.Now.AddDays(1d),Cache.NoSlidingExpiration);
}
}
返回列表;
}
一旦数据在缓存中,并发线程将只等待取出数据的时间,即这行代码:
katList = appCache[CachedData.NaviDataKey] as List<Kategorie>;
katList=appCache[CachedData.NaviDataKey]作为列表;
因此,性能成本不会太大。如果两个线程在数据为null时同时进入该方法,则第一个线程将进入锁。第二个将等待锁“解锁”,然后进入
katlist
在他的情况下仍然是空的,因此将调用两次DataTools.BuildKeteGorientiteList
。所以基本上,我认为lock
语句必须包含方法的所有内容,除了return
。实际上不是。在您的场景中,当两个线程进入锁时,只有一个线程将进入锁,第二个线程将等待。因此,当第一个线程加载缓存并退出(解锁锁)时,第二个线程将进入,这就是为什么您有第二个if(katlist==null),所以由于第一个线程更新了它,它将不会为null,第二个线程将不会重新加载缓存。这是进行同步的最安全的方法。这称为双重检查锁定。在您的维基百科示例中了解更多信息,双重检查是在静态bool上完成的。在您提供的代码中,katList
只是一个局部变量。如果在进入锁之前它是空的,那么在进入锁时它仍然是空的。好的一点,我没有意识到这是局部变量,而不是类或全局变量+祝你好运。我将更改我的代码。这是否存在性能问题,或者在第一个用户获得缓存后,以下所有用户都将被释放到缓存中。使用此设置使站点崩溃是否存在任何问题。