2016-06-02 10 views
0

У меня есть следующий репозиторий с кэшемКак сделать кэш-хранилище поточно

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

Я хотел бы запустить метод GetUser() в разных потоках, поэтому мне нужно, чтобы сделать этот метод поточно.
Как я могу это сделать?
Я не вижу никакого изящного решения, только lock (someObject) для всего тела метода. Но в результате я не добьюсь никакого прироста производительности

+2

Не можете ли вы использовать кеш .Net framework? https://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache(v=vs.110).aspx Он полностью потокобезопасен. См. Http://blog.falafel.com/working-system-runtime-caching-memorycache/ для примера –

+0

@PeterBons MemoryCache, конечно, может использоваться вместо моего индивидуального подхода со списком и CreationDate. Но некоторые блокировки по-прежнему необходимы. – Disappointed

+0

Является ли ваш репозиторий самонезависимым? Если нет, то мне кажется бессмысленным. – Maarten

ответ

0

Мы обычно делаем это с ReaderWriterLockSlim вроде этого:

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

С этим, несколько потоков будут иметь возможность читать кэш одновременно, но если он должен напишите, он будет заблокирован.

Отказ от ответственности: я не запустил код, чтобы проверить его;)

+0

Спасибо за ответ. Нужно ли мне использовать некоторую потокобезопасную коллекцию для объектов кэша вместо списка (как сейчас)? – Disappointed

+0

Вы можете использовать Список, так как вы гарантируете возможность записи в список только по 1 теме за раз. Вы должны будете использовать блокировку записи при удалении элемента из кеша и, возможно, хотите повторно проверить, не добавил ли элемент (другим потоком) в блокировке записи – Philippe

+0

«Вы можете использовать Список, так как вы гарантируете доступ на запись к списку только по 1 теме за раз ». Могу ли я быть уверенным, что несколько одновременных чтений из списка не будут разбиваться? – Disappointed

 Смежные вопросы

  • Нет связанных вопросов^_^