C# 锁定以将数据加载到缓存

C# 锁定以将数据加载到缓存,c#,asp.net,.net-4.0,locking,C#,Asp.net,.net 4.0,Locking,我在web应用程序中有一个helper类,它所做的其中一件事就是将常见的、不变的数据对象表示为静态属性。我正在加载这些对象,如下所示: public static class MyWebHelper { #region - Constants & Fields private const string LocationCacheKey = "MyWebHelper_Locations"; private static object LocationLoadLoc

我在web应用程序中有一个helper类,它所做的其中一件事就是将常见的、不变的数据对象表示为静态属性。我正在加载这些对象,如下所示:

public static class MyWebHelper
{
    #region - Constants & Fields 
    private const string LocationCacheKey = "MyWebHelper_Locations";
    private static object LocationLoadLock = new object();
    private static MemoryCache m_SiteCache;
    #endregion

    #region - Properties 
    /// <summary>
    /// Gets the uneditable collection of locations.
    /// </summary>
    public static ReadOnlyCollection<Location> Locations
    {
        get
        {
            EnsureLocationsCache();
            return (ReadOnlyCollection<Location>)SiteCache[LocationCacheKey];
        }
    }

    /// <summary>
    /// Gets the MemoryCache object specific for my site cache.
    /// </summary>
    public static MemoryCache SiteCache
    {
        get
        {
            if (m_SiteCache == null)
            {
                m_SiteCache = new MemoryCache("MyWeb_SiteCache");
            }
            return m_SiteCache;
        }
    }
    #endregion

    #region - Methods 
    private static void EnsureLocationsCache()
    {
        lock (LocationLoadLock)
        {
            if (SiteCache[LocationCacheKey] == null)
            {
                //
                // Load all Locations from the database and perform default sorting on location code.
                //
                List<Location> lLocations = DataAccess.GetAllLocations();
                lLocations.Sort(delegate(Location l1, Location l2) { return string.CompareOrdinal(l1.Code, l2.Code); });
                SiteCache[LocationCacheKey] = new ReadOnlyCollection<Location>(lLocations);
            }
        }
    }
    #endregion
}
公共静态类MyWebHelper
{
#区域-常量和字段
private const string LocationCacheKey=“MyWebHelper\u Locations”;
私有静态对象LocationLoadLock=新对象();
私有静态内存缓存m_SiteCache;
#端区
#区域属性
/// 
///获取不可编辑的位置集合。
/// 
公共静态只读收集位置
{
得到
{
确保重新定位缓存();
返回(ReadOnlyCollection)SiteCache[LocationCacheKey];
}
}
/// 
///获取特定于我的站点缓存的MemoryCache对象。
/// 
公共静态内存缓存SiteCache
{
得到
{
如果(m_SiteCache==null)
{
m_SiteCache=新内存缓存(“MyWeb_SiteCache”);
}
返回m_SiteCache;
}
}
#端区
#区域-方法
私有静态void-RelocationsCache()
{
锁(位置LoadLock)
{
if(SiteCache[LocationCacheKey]==null)
{
//
//从数据库加载所有位置,并对位置代码执行默认排序。
//
List lLocations=DataAccess.GetAllLocations();
排序(委托(位置l1,位置l2){返回字符串.CompareOrdinal(l1.Code,l2.Code);});
SiteCache[LocationCacheKey]=新的只读集合(分配);
}
}
}
#端区
}

我的问题是,锁定有什么帮助吗?我试图减少对数据库的调用,但锁定是否只是引入了开销?缓存的数据在整个站点中使用得非常普遍,而且几乎不会更改。另外,我是否锁定在正确的位置?

由于对服务器的每个web请求都在创建一个新线程,并且您的静态助手在这些线程之间共享,因此某种类型的锁定很有用。如果没有锁,则在数据库读取已经在进行时,您将冒着进入EnsueRelocationsCache方法的风险。现在,这可能不会影响两次加载的数据的正确性,但如果DB读取成本很高,则会影响整体性能,并抵消缓存的效果

这实际上取决于在应用程序启动时尝试访问EnsureRelocationsCache()方法的并发线程的数量,这可能很低,因为它是对每个LocationCacheKey的一次性调用

获取锁的开销是一个值得关注的问题,因为即使缓存已经加载,代码也会获取锁@asawyer、@TomTom和@Joe提出了一些替代方案

编辑:


考虑在中调用EnsureRelocationsCache()。在这种情况下,您根本不需要锁


.

如果您进行了锁定,我会使用,这样您就不会在每次从缓存读取时产生获取锁的开销

我还质疑是否需要锁。如果不锁定,则多个线程可能会同时尝试刷新缓存,但这种情况很少发生,除非非常昂贵,否则不会对静态只读数据造成问题


如果它很昂贵,那么您可以按照@asawyer注释中的建议,在缓存中插入一个
Lazy
,这样锁定将由
Lazy

处理。您可以查看Lazy,它将帮助抽象出锁定机制。@asawyer我编写了一个针对Lazy解决方案的测试,如果操作正确,我想检查一下。我应该把这些代码发布为这个问题的编辑,这个问题的答案,还是新问题的全部内容?-1用于输入现实。不需要锁,使用ReadWriterLock可以更好地实现这一点,只有在需要db访问时才会升级到完全锁。大部分时间加载的数据都会被命中,而只读锁更有效(允许并行读取,仅在需要写入缓存时才进行序列化)。你甚至可以少锁一把。Lock很好,很简单——从技术上说,它是一个沉重的解决方案,这是一个令人遗憾的幼稚。@TomTom ReadWriterLock解决方案是在检查null之前调用EnterReadLock,然后EnterWriteLock从db读取到缓存中吗?对于整个社区,我是否接受了错误的答案?如果是,联合国是否接受这一做法?不确定是否有任何影响。在加载情况下,是的-在读取之前应该设置一个读锁,因为如果另一个线程具有写锁,读锁将停止。因此,除非写入某些内容,否则100个线程可以同时读取。如果缓存中通常填充了正确的答案。。。这样比较好。不过,这在很大程度上取决于性能——我写的东西每秒运行数十万个缓存循环。天真的解决方案不会让你走得更远。@zooone9243这就是我问题的目的。我不会反对您编辑您的答案以使其更完整,并保留所选答案的状态。但是,不确定这是否违反了某些策略。“考虑在静态构造函数中调用EnsureRelocationsCache(),而不是在每次调用Locations属性时调用”-如果从MemoryCache中逐出该项,这将不起作用。
EnsureLocaltionsCache
的全部目的是确保缓存在再次需要时自动刷新。