How can I reduce locking for readers in generic cached repository?

258 Views Asked by At

I have created a generic CachedRepository that looks like the following:

public class CachedRepository<T> : ICachedRepository, IRepository<T> where T : BaseModel, new()
{
        private static readonly Lock<T> TypeLock = new Lock<T>();

        public void DeleteBatch(IQueryable<T> entities)
        {
            lock (TypeLock)
            {
                // delete logic here
            }
        }

        public T GetNoTracking(int id)
        {
            lock (TypeLock)
            {
                // fetch by id logic here
            }
        }
}

By using a generic object to lock on, I will obtain locking per type (i.e. threads working with different types will not wait one after the other). However, this class is used for lists of objects that are rarely changed, but heavily read, so using simple locking with lock will delay a reader until another reader is done.

I had a look upon ReaderWriterLockSlim, but it is not a generic class and if I use it I lose the type locking I have now.

Question: How can I reduce locking for readers while keeping the type locking that lock(generic_type_instance) pattern is providing?

2

There are 2 best solutions below

1
On BEST ANSWER

The lock doesn't need to be generic. The class that the object is contained in is generic, so it will have different static variables for each generic type input of CachedRepository already, regardless of whether the static object is also generic. Just use a regular ReaderWriterLockSlim as the static variable and your code will work fine.

1
On

NOTE: @Servy is absolutely right. This abstraction in the example is just overhead for your scenario.

You can wrap the ReaderWriterLockSlim into a generic class. Expose the Enter, Exit methods for the read and write locks of the ReaderWriterLockSlim to be able to use it correctly.

Just a sample:

        internal class ReaderFreadlyLock<T>
        {
            private readonly ReaderWriterLockSlim lck = new ReaderWriterLockSlim();

            public T Read(Func<T> func)
            {
                this.lck.EnterReadLock();
                try
                {
                    return func();
                }
                finally
                {
                    this.lck.ExitReadLock();
                }
            }

            public void Write(Action action)
            {

                this.lck.EnterReadLock();
                try
                {
                    action();
                }
                finally
                {
                    this.lck.ExitWriteLock();
                }
            }
        }

Then you can use it like this:

var chached = TypeLock.Read(() => {
    // read from cache 
});


TypeLock.Write(() => {    
    // write to db    
    // write to cache 
});