如何使缓存存储库线程安全

时间:2016-06-02 08:06:23

标签: c# .net multithreading repository-pattern

我有以下带缓存的存储库

  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)到整个方法体。但结果我不会获得任何性能提升

1 个答案:

答案 0 :(得分: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;
    }
}

有了这个,多个线程将能够同时读取缓存,但如果必须写入,它将被锁定。

免责声明:我没有运行代码来检查它;)