2016-06-02 3 views
0

Ich habe das folgendes Repository mit CacheWie Cache-Repository Thread-sicher

public class User 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public string LastName { get; set; } 
    public DateTime DateOfBirth { get; set; } 
} 

public interface IUserRepository 
{ 
    User GetUser(int userId); 
} 

public class CacheObject 
{ 
    public int UserId { get; set; } 
    public User User { get; set; } 
    public DateTime CreationDate { get; set; } 
} 

public class CachedUserRepository : IUserRepository 
{ 
    private IUserRepository _userRepository; 

    private List<CacheObject> _cache = new List<CacheObject>(); 

    private int _cacheDuration = 60; 

    public CachedUserRepository(IUserRepository userRepository) 
    { 
     _userRepository = userRepository; 
    } 
    public User GetUser(int userId) 
    { 
     bool addToCache = false; 
     CacheObject valueFromCache = _cache.SingleOrDefault(u => u.UserId == userId); 
     // user was found 
     if (valueFromCache != null) 
     { 
      // if cache is outdated then remove value from it 
      if (valueFromCache.CreationDate.AddSeconds(_cacheDuration) < DateTime.Now) 
      { 
       _cache.Remove(valueFromCache); 
       addToCache = true; 
      } 
      else { 
       // update cache date 
       valueFromCache.CreationDate = DateTime.Now; 
       return valueFromCache.User; 
      } 
     } 
     // user is absent in cache 
     else { 
      addToCache = true; 
     } 

     if (addToCache) 
     { 
      User result = _userRepository.GetUser(userId); 
      _cache.Add(new CacheObject() { User = result, UserId = userId, CreationDate = DateTime.Now }); 
      return result; 
     } 

     return null; 
    } 
} 

Ich möchte laufen Methode GetUser() in verschiedenen Threads machen, so dass ich diese Methode Thread-sicher machen müssen.
Wie kann ich es schaffen?
Ich sehe keine elegante Lösung, nur Sperre (someObject) auf den gesamten Körper der Methode. Aber als Ergebnis werde ich keine Leistungssteigerung erzielen

+2

Kannst du nicht den .Net Framework bereitgestellten Cache verwenden? https://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache(v=vs.110).aspx Es ist völlig threadsicher. Ein Beispiel finden Sie unter http://blog.falafel.com/working-system-runtime-caching-memorycache/ –

+0

@PeterBons MemoryCache kann natürlich anstelle meiner benutzerdefinierten Ansatz mit List und CreationDate verwendet werden. Aber einige Sperren sind noch erforderlich. – Disappointed

+0

Ist Ihr Repository selbst Thread-sicher? Wenn nicht, dann erscheint es mir sinnlos. – Maarten

Antwort

0

Wir tun dies in der Regel mit einem ReaderWriterLockSlim wie folgt aus:

public class CachedUserRepository : IUserRepository 
{ 
    private readonly ReaderWriterLockSlim _cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); 
    private IUserRepository _userRepository; 

    private List<CacheObject> _cache = new List<CacheObject>(); 

    private int _cacheDuration = 60; 

    public CachedUserRepository(IUserRepository userRepository) 
    { 
     _userRepository = userRepository; 
    } 
    public User GetUser(int userId) 
    { 
     bool addToCache = false; 
     // Enter an upgradeable read lock because we might have to use a write lock if having to update the cache 
     // Multiple threads can read the cache at the same time 
     _cacheLock.EnterUpgradeableReadLock(); 
     try 
     { 
      CacheObject valueFromCache = _cache.SingleOrDefault(u => u.UserId == userId); 
      // user was found 
      if (valueFromCache != null) 
      { 
       // if cache is outdated then remove value from it 
       if (valueFromCache.CreationDate.AddSeconds(_cacheDuration) < DateTime.Now) 
       { 
        // Upgrade to a write lock, as an item has to be removed from the cache. 
        // We will only enter the write lock if nobody holds either a read or write lock 
        _cacheLock.EnterWriteLock(); 
        try 
        { 
         _cache.Remove(valueFromCache); 
        } 
        finally 
        { 
         _cacheLock.ExitWriteLock(); 
        } 
        addToCache = true; 
       } 
       else 
       { 
        // update cache date 
        valueFromCache.CreationDate = DateTime.Now; 
        return valueFromCache.User; 
       } 
      } 
      // user is absent in cache 
      else 
      { 
       addToCache = true; 
      } 

      if (addToCache) 
      { 
       User result = _userRepository.GetUser(userId); 
       // Upgrade to a write lock, as an item will (probably) be added to the cache. 
       // We will only enter the write lock if nobody holds either a read or write lock 
       _cacheLock.EnterWriteLock(); 
       try 
       { 
        if (_cache.Any(u => u.UserId != userId)) 
        { 
         _cache.Add(new CacheObject() {User = result, UserId = userId, CreationDate = DateTime.Now}); 
        } 
       } 
       finally 
       { 
        _cacheLock.ExitWriteLock(); 
       } 
       return result; 
      } 
     } 
     finally 
     { 
      _cacheLock.ExitUpgradeableReadLock(); 
     } 

     return null; 
    } 
} 

Damit wird mehrere Threads der Lage sein, den Cache gleichzeitig zu lesen, aber wenn es muss geschrieben werden, wird es gesperrt.

Haftungsausschluss: Ich habe den Code nicht ausgeführt, um es zu überprüfen;)

+0

Danke für die Antwort. Muss ich eine threadsichere Auflistung für Cache-Objekte anstelle von List (wie es jetzt ist) verwenden? – Disappointed

+0

Sie können eine Liste verwenden, da Sie den Schreibzugriff auf die Liste jeweils nur für einen Thread garantieren können. Sie müssen eine Schreibsperre verwenden, wenn Sie ein Objekt aus dem Cache entfernen und wahrscheinlich erneut prüfen möchten, ob das Objekt in der Schreibsperre bereits (durch einen anderen Thread) hinzugefügt wurde. – Philippe

+0

"Sie können eine Liste verwenden, wie Sie garantieren Schreibzugriff auf die Liste durch nur 1 Thread gleichzeitig. Kann ich sicher sein, dass mehrere gleichzeitige Lesevorgänge aus der Liste nicht abstürzen? – Disappointed