How to implement a thread-safe caching mechanism when working with collections?

Scenario

  • I have a bunch of Child objects related to this Parent .
  • I am working on an ASP.NET MVC 3 web application (e.g. multi-threaded).
  • One of the pages is the “search” page, where I need to capture a given subset of children and “do things” in their memory (calculations, ordering, listing).
  • Instead of making every single separate call, I make one database call to get all the children for a given parent, cache the result and "make stuff" as a result.
  • The problem is that "do stuff" includes LINQ operations (listing, adding / removing items from the collection), which when implemented with the help List<T>are not thread safe.

I read about ConcurrentBag<T>and ConcurrentDictionary<T>, but not sure if I should use one of them or implement synchronization / locking.

I am on .NET 4, so I use it ObjectCacheas a single-screen instance MemoryCache.Default. I have a service level that works with a cache, and services accept an instance ObjectCachethat runs through the DI constructor. Thus, all services have the same instance ObjectCache.

- "" , , , , , , , .

?

+3
3

, , List<T> . A Dictionary<TKey, TValue> - , :

var value = instance.GetValueExpensive(key);

:

var value = instance.GetValueCached(key);

, . .

, .NET4 ConcurrentDictionary<TKey, TValue>, . ? , , - .

, - , . ?

, , LINQ, , IEnumerable<T> , , List<T>. , , ? List<T> , . ?

. , LINQ " ", , /, . - , , .

, , List<T> , . List<T>.AsReadOnly, .

, List<T> , . , , , ( ), , List<T> , .

, List<T>, :

, , .

+8

RefreshFooCache :

    public ReadOnlyCollection<Foo> RefreshFooCache(Foo parentFoo)
       {
         ReadOnlyCollection<Foo> results;


        try {
// Prevent other writers but allow read operations to proceed
        Lock.EnterUpgradableReaderLock();

    // Recheck your cache: it may have already been updated by a previous thread before we gained exclusive lock
    if (_cache.Get(parentFoo.Id.ToString()) != null) {
      return parentFoo.FindFoosUnderneath(uniqueUri).AsReadOnly();
    }

        // Get the parent and everything below.
        var parentFooIncludingBelow = _repo.FindFoosIncludingBelow(parentFoo.UniqueUri).ToList();

// Now prevent both other writers and other readers
        Lock.EnterWriteLock();

        // Remove the cache
        _cache.Remove(parentFoo.Id.ToString());

        // Add the cache.
        _cache.Add(parentFoo.Id.ToString(), parentFooIncludingBelow );


       } finally {
    if (Lock.IsWriteLockHeld) {
            Lock.ExitWriteLock();
    }
        Lock.ExitUpgradableReaderLock();
       }
        results = parentFooIncludingBelow.AsReadOnly();
        return results;
       }
+2

EDIT - ReadOnlyCollection ConcurrentDictionary

. . , , :

public class FooService
{
   private static ReaderWriterLockSlim _lock;
   private static readonly object SyncLock = new object();

   private static ReaderWriterLockSlim Lock
   {
      get
      {
         if (_lock == null)
         {
           lock(SyncLock)
           {
              if (_lock == null)
                 _lock = new ReaderWriterLockSlim();
           }
         } 

         return _lock;
      }
   }

   public ReadOnlyCollection<Foo> RefreshFooCache(Foo parentFoo)
   {
       // Get the parent and everything below.
       var parentFooIncludingBelow = repo.FindFoosIncludingBelow(parentFoo.UniqueUri).ToList().AsReadOnly();

       try
       {
          Lock.EnterWriteLock();

          // Remove the cache
         _cache.Remove(parentFoo.Id.ToString());

         // Add the cache.
         _cache.Add(parentFoo.Id.ToString(), parentFooIncludingBelow);
       }
       finally
       {
          Lock.ExitWriteLock();
       }

       return parentFooIncludingBelow;
   }

   public ReadOnlyCollection<Foo> FindFoo(string uniqueUri)
   {
      var parentIdForFoo = uniqueUri.GetParentId();
      ReadOnlyCollection<Foo> results;

      try
      {
         Lock.EnterReadLock();
         var cachedFoo = _cache.Get(parentIdForFoo);

         if (cachedFoo != null)
            results = cachedFoo.FindFoosUnderneath(uniqueUri).ToList().AsReadOnly();
      }
      finally
      {
         Lock.ExitReadLock();
      }

      if (results == null)
         results = RefreshFooCache(parentFoo).FindFoosUnderneath(uniqueUri).ToList().AsReadOnly();
      }

      return results;
   }
}

:

  • (, HTTP-) FooService.
  • FooService , HTTP- .
  • ReaderWriterLockSlim , , . , "" . "" , , , . - .
  • , "Foo" , - .

// ? , . LINQ , , .

+1

All Articles