1

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?

Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
  • Is this going to be a memory repository or a db repository? – Gusman Sep 22 '17 at 11:36
  • I'm not sure that you can if this is an in-memory object. One possibility is to allow dirty reads but it depends on the underlying collection you're using. There are a set of pre-rolled collections to use in concurrent scenarios to handle these. Have you evaluated them? – DiskJunky Sep 22 '17 at 11:39
  • @Gusman - all data is stored in memory (`MemoryCache.Default`) and all writes are also pushed into the database. So, reads will almost never reach the database and writes will always do. – Alexei - check Codidact Sep 22 '17 at 11:39
  • @DiskJunky - currently I am using a key for each type in `MemoryCache.Default` and I am storing a `Dictionary` (all my models have a single integer unique key) – Alexei - check Codidact Sep 22 '17 at 11:41
  • Would it not make more sense to use `ConcurrentDictionary` rather than `Dictionary`? That way you don't need to roll your own locking mechanism; https://msdn.microsoft.com/en-us/library/dd287191(v=vs.110).aspx – DiskJunky Sep 22 '17 at 11:43
  • @DiskJunky - yes, it make senses. I don't remember why I have chosen custom locking instead of `ConcurrentDictionary`, but I think it is related to [this answer](https://stackoverflow.com/a/1949182/2780791). Basically some operations are not atomic (e.g. Bulk inserts) and I have to use custom locking. – Alexei - check Codidact Sep 22 '17 at 11:47
  • That's what I was going to recommend, if you are going to store in memory use a concurrent queue or dictionary, that will avoid totally the lock. – Gusman Sep 22 '17 at 11:50

2 Answers2

4

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.

Servy
  • 202,030
  • 26
  • 332
  • 449
0

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 
});
vasil oreshenski
  • 2,788
  • 1
  • 14
  • 21