【问题标题】:Cache locking with static object in generic base class在通用基类中使用静态对象进行缓存锁定
【发布时间】:2013-10-05 03:01:44
【问题描述】:

我有一个用于值缓存功能的通用基类。

public abstract class CachedValueProviderBase<T> : ICachedValueProvider<T> where T : class
{
    private Cache Cache { set; get; }
    protected string CacheKey { get; set; }
    protected int CacheSpanInMinutes { get; set; }

    private static readonly object _cacheLock = new object();

    public T Values
    {
        get
        {
            T value = Cache[CacheKey] as T;
            if (value == null)
            {
                lock (_cacheLock)
                {
                    value = Cache[CacheKey] as T;
                    if (value == null)
                    {
                        value = InitializeCache();
                    }
                }
            }

            return value;
        }
    }

    protected CachedValueProviderBase()
    {
        Cache = HttpRuntime.Cache;
        CacheSpanInMinutes = 15;
    }

    public T CacheValue(T value)
    {
        if (value != null)
        {
            lock (_cacheLock)
            {
                Cache.Insert(CacheKey, value, null, DateTime.UtcNow.AddMinutes(CacheSpanInMinutes),
                             Cache.NoSlidingExpiration);
            }
        }

        return value;
    }

    private T InitializeCache()
    {
        T value = Initialize();
        CacheValue(value);

        return value;
    }

    protected abstract T Initialize();
}

我有几个使用这个基类的类,只要 T 不同就可以了。例如,当两个子类使用相同的 T,字符串时,它们共享相同的缓存锁定对象。在基类中实现逻辑但仍为每个子类提供自己的缓存锁定对象的最佳方法是什么?

更新 根据以下建议,我更新了我的课程:

public abstract class CachedValueProviderBase<T> : ICachedValueProvider<T> where T : class
    {
        private Cache Cache { set; get; }
        protected string CacheKey { get; set; }
        protected int CacheSpanInMinutes { get; set; }
        private object _cacheLock = new object();

        public T Values
        {
            get
            {
                T value = Cache[CacheKey] as T;
                if (value == null)
                {
                    lock (_cacheLock)
                    {
                        value = Cache[CacheKey] as T;
                        if (value == null)
                        {
                            value = InitializeCache();
                        }
                    }
                }

                return value;
            }
        }

        protected CachedValueProviderBase()
        {
            Cache = HttpRuntime.Cache;
            CacheSpanInMinutes = 15;
        }

        public T CacheValue(T value)
        {
            if (value != null)
            {
                Cache.Insert(CacheKey, value, null, DateTime.UtcNow.AddMinutes(CacheSpanInMinutes),
                             Cache.NoSlidingExpiration);

            }

            return value;
        }

        private T InitializeCache()
        {
            T value = Initialize();
            CacheValue(value);

            return value;
        }

        protected abstract T Initialize();
    }
}

我的子类现在是单例的,所以我可以摆脱静态缓存锁对象,使其成为实例变量。

【问题讨论】:

    标签: c# generics caching


    【解决方案1】:

    我必须仔细查看您的代码,以确定它是否正确。一旦我注意到您的缓存是HttpRuntime.Cache,这很有意义。 HttpRuntime.Cache 是线程安全的。否则你会有几个线程安全问题。使用您当前的代码,我建议您执行以下操作:

    private string CacheKey { get; set; }
    
    protected CachedValueProviderBase(string cacheKey)
    {
        this.CacheKey = cacheKey + "_" + typeof(T).FullName;
    }
    

    通过提供cacheKey 作为构造函数参数并将属性设为私有(或只读),您可以防止它在以后被更改。通过将类型名称附加到键中,您可以防止缓存冲突,因为每个人都使用相同的缓存。

    最后一点。 CacheValue 方法中的 lock 是多余的,因为 Cache 是线程安全的。

    【讨论】:

    • 所以看到 Cache 是线程安全的,我基本上可以摆脱缓存锁定对象了?
    • 视情况而定。在查看您的代码时,我想到 Values 被多个线程调用,并且您想防止 InitializeCache 被多次调用。如果是这种情况,则锁已到位。如果您不关心该值被多次创建(并且一个被丢弃),那么在这种情况下,锁是多余的。
    • Steven,我在我的子类中提供了缓存键,并确保它们不使用相同的键。
    【解决方案2】:

    好吧,只需删除 cacheLock 对象上的 static 修饰符。

    该关键字强制该字段在共享相同通用参数类型的子类的所有实例之间共享。

    如果您删除它,cacheLock 对象将是子类的每个实例私有的,无论泛型参数的类型如何。

     private static readonly object _cacheLock = new object();
    

    应该是:

     private readonly object _cacheLock = new object();
    

    希望有帮助

    【讨论】:

    • 这是在网络服务器上运行的,所以我仍然需要为多个线程锁定缓存。删除静态时,可能会有多个线程初始化缓存。
    • 那么你应该考虑为你的缓存提供者使用singleton pattern,这样无论线程访问它,你都有一个缓存,同时为每个缓存实例保持一个唯一的缓存锁。
    • @b3n 如果您使用.net 4.0,您可以使用Lazy&lt;T&gt; 进行线程安全初始化。无论如何,实例方法的静态锁是的事情。
    • 如果我遵循单例路径,如何将我的代码保留在基类中?我必须将缓存锁拉到子类,这样基类就会在它试图锁定的地方中断。
    • 没有。单例模式与此无关,它用于保证您在整个应用程序中拥有唯一的对象实例。但是,您需要使用单例模式的线程安全实现。它不会破坏您的缓存提供程序实现。
    【解决方案3】:

    我通过在我的基类 GetCacheLockObject() 中实现一个抽象方法来处理这个问题。

    protected abstract object GetCacheLockObject();
    

    然后每个派生类返回它自己对缓存锁对象的引用:

    private static readonly object _cacheLockObject = new Object();
    
    protected override object GetCacheLockObject()
    {
        return _cacheLockObject;
    }
    

    调用以锁定共享基类缓存代码,然后引用此方法而不是基类中的对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-25
      • 2013-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多