简体   繁体   中英

Cache locking with static object in generic base class

I have a generic base class for value caching functionality.

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

I have several classes that make use of this base class and as long as the T is different it is fine. When two sub classes use the same T, string for example, they share the same cache lock object. What is the best way of implementing the logic in a base class but still giving each sub class it's own cache lock object?

Update After the suggestions below I have updated my class:

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

My sub classes are now singletons so I could get rid of the static cachelock object making it an instance variable.

Well, just remove the static modifier on your cacheLock object.

That keyword forces the field to be shared between all instances of subclasses that share the same generic parameter type.

If you remove it, the cacheLock object will be private to each instance of a subclass, regardless of the generic parameter's type.

 private static readonly object _cacheLock = new object();

Should be :

 private readonly object _cacheLock = new object();

Hope that helps

I had to take a good look at your code, to find out if it was correct. Once I noticed your cache is a HttpRuntime.Cache , it made sense. The HttpRuntime.Cache is thread-safe. Otherwise you would have had several thread-safety problems. With your current, code I advice you to do the following:

private string CacheKey { get; set; }

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

By supplying the cacheKey as constructor argument and making the property private (or readonly would do), you prevent it from being changed by later on. By appending the type name to the key, you prevent cache conflicts since everybody is using the same cache.

One last note. The lock in the CacheValue method is redundant, since the Cache is thread-safe.

I handled this by implementing an abstract method in my base class GetCacheLockObject().

protected abstract object GetCacheLockObject();

Each derived class then returns its own reference to the cache lock object:

private static readonly object _cacheLockObject = new Object();

protected override object GetCacheLockObject()
{
    return _cacheLockObject;
}

Calls to lock in the shared base class caching code then reference this method rather than an object in the base class.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM