C# 正在寻找一种在缓存时减少锁定的方法

C# 正在寻找一种在缓存时减少锁定的方法,c#,.net,.net-core,C#,.net,.net Core,我正在使用下面的代码来缓存项目。这很基本 我遇到的问题是,每次它缓存一个项目时,代码部分都会锁定。因此,大约每小时有一百万件物品到达,这是一个问题 我试着为每个cacheKey创建一个静态锁对象的字典,这样锁定是细粒度的,但这本身就成了管理它们过期的问题,等等 有没有更好的方法来实现最小锁定 private static readonly object cacheLock = new object(); public static T GetFromCache<T>(string c

我正在使用下面的代码来缓存项目。这很基本

我遇到的问题是,每次它缓存一个项目时,代码部分都会锁定。因此,大约每小时有一百万件物品到达,这是一个问题

我试着为每个cacheKey创建一个静态锁对象的字典,这样锁定是细粒度的,但这本身就成了管理它们过期的问题,等等

有没有更好的方法来实现最小锁定

private static readonly object cacheLock = new object();
public static T GetFromCache<T>(string cacheKey, Func<T> GetData) where T : class {

    // Returns null if the string does not exist, prevents a race condition
    // where the cache invalidates between the contains check and the retrieval.
    T cachedData = MemoryCache.Default.Get(cacheKey) as T;

    if (cachedData != null) {
        return cachedData;
    }

    lock (cacheLock) {
        // Check to see if anyone wrote to the cache while we where
        // waiting our turn to write the new value.
        cachedData = MemoryCache.Default.Get(cacheKey) as T;

        if (cachedData != null) {
            return cachedData;
        }

        // The value still did not exist so we now write it in to the cache.
        cachedData = GetData();

        MemoryCache.Default.Set(cacheKey, cachedData, new CacheItemPolicy(...));
        return cachedData;
    }
}

您可能想考虑使用,只有在需要时才能获得写锁。

使用cacheLock.entereadlock;和cacheLock.EnterWriteLock;应该大大提高性能

我给出的链接甚至有一个缓存示例,正是您需要的,我复制到这里:

public class SynchronizedCache 
{
    private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
    private Dictionary<int, string> innerCache = new Dictionary<int, string>();

    public int Count
    { get { return innerCache.Count; } }

    public string Read(int key)
    {
        cacheLock.EnterReadLock();
        try
        {
            return innerCache[key];
        }
        finally
        {
            cacheLock.ExitReadLock();
        }
    }

    public void Add(int key, string value)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Add(key, value);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }

    public bool AddWithTimeout(int key, string value, int timeout)
    {
        if (cacheLock.TryEnterWriteLock(timeout))
        {
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
            return true;
        }
        else
        {
            return false;
        }
    }

    public AddOrUpdateStatus AddOrUpdate(int key, string value)
    {
        cacheLock.EnterUpgradeableReadLock();
        try
        {
            string result = null;
            if (innerCache.TryGetValue(key, out result))
            {
                if (result == value)
                {
                    return AddOrUpdateStatus.Unchanged;
                }
                else
                {
                    cacheLock.EnterWriteLock();
                    try
                    {
                        innerCache[key] = value;
                    }
                    finally
                    {
                        cacheLock.ExitWriteLock();
                    }
                    return AddOrUpdateStatus.Updated;
                }
            }
            else
            {
                cacheLock.EnterWriteLock();
                try
                {
                    innerCache.Add(key, value);
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
                return AddOrUpdateStatus.Added;
            }
        }
        finally
        {
            cacheLock.ExitUpgradeableReadLock();
        }
    }

    public void Delete(int key)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Remove(key);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }

    public enum AddOrUpdateStatus
    {
        Added,
        Updated,
        Unchanged
    };

    ~SynchronizedCache()
    {
       if (cacheLock != null) cacheLock.Dispose();
    }
}

您可能想考虑使用,只有在需要时才能获得写锁。

使用cacheLock.entereadlock;和cacheLock.EnterWriteLock;应该大大提高性能

我给出的链接甚至有一个缓存示例,正是您需要的,我复制到这里:

public class SynchronizedCache 
{
    private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
    private Dictionary<int, string> innerCache = new Dictionary<int, string>();

    public int Count
    { get { return innerCache.Count; } }

    public string Read(int key)
    {
        cacheLock.EnterReadLock();
        try
        {
            return innerCache[key];
        }
        finally
        {
            cacheLock.ExitReadLock();
        }
    }

    public void Add(int key, string value)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Add(key, value);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }

    public bool AddWithTimeout(int key, string value, int timeout)
    {
        if (cacheLock.TryEnterWriteLock(timeout))
        {
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
            return true;
        }
        else
        {
            return false;
        }
    }

    public AddOrUpdateStatus AddOrUpdate(int key, string value)
    {
        cacheLock.EnterUpgradeableReadLock();
        try
        {
            string result = null;
            if (innerCache.TryGetValue(key, out result))
            {
                if (result == value)
                {
                    return AddOrUpdateStatus.Unchanged;
                }
                else
                {
                    cacheLock.EnterWriteLock();
                    try
                    {
                        innerCache[key] = value;
                    }
                    finally
                    {
                        cacheLock.ExitWriteLock();
                    }
                    return AddOrUpdateStatus.Updated;
                }
            }
            else
            {
                cacheLock.EnterWriteLock();
                try
                {
                    innerCache.Add(key, value);
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
                return AddOrUpdateStatus.Added;
            }
        }
        finally
        {
            cacheLock.ExitUpgradeableReadLock();
        }
    }

    public void Delete(int key)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Remove(key);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }

    public enum AddOrUpdateStatus
    {
        Added,
        Updated,
        Unchanged
    };

    ~SynchronizedCache()
    {
       if (cacheLock != null) cacheLock.Dispose();
    }
}
我不知道MemoryCache.Default是如何实现的,也不知道您是否可以控制它。 但一般来说,在多线程环境中,更喜欢使用ConcurrentDictionary而不是带锁的Dictionary

GetFromCache将成为

ConcurrentDictionary<string, T> cache = new ConcurrentDictionary<string, T>();
...
cache.GetOrAdd("someKey", (key) =>
{
  var data = PullDataFromDatabase(key);
  return data;
});
并将缓存存储为具有定义到期日的CacheItem

cache.GetOrAdd("someKey", (key) =>
{
    var data = PullDataFromDatabase(key);
    return new CacheItem<T>() { Item = data, Expiry = DateTime.UtcNow.Add(TimeSpan.FromHours(1)) };
});
解释

在同时请求的情况下,使用GetOrAdd,如果不在缓存中,您可能会多次调用get from数据库委托。 但是,GetOrAdd最终将只使用委托返回的一个值,通过返回一个Lazy,您可以保证只调用一个Lazy。

我不知道MemoryCache.Default是如何实现的,或者您是否可以控制它。 但一般来说,在多线程环境中,更喜欢使用ConcurrentDictionary而不是带锁的Dictionary

GetFromCache将成为

ConcurrentDictionary<string, T> cache = new ConcurrentDictionary<string, T>();
...
cache.GetOrAdd("someKey", (key) =>
{
  var data = PullDataFromDatabase(key);
  return data;
});
并将缓存存储为具有定义到期日的CacheItem

cache.GetOrAdd("someKey", (key) =>
{
    var data = PullDataFromDatabase(key);
    return new CacheItem<T>() { Item = data, Expiry = DateTime.UtcNow.Add(TimeSpan.FromHours(1)) };
});
解释

在同时请求的情况下,使用GetOrAdd,如果不在缓存中,您可能会多次调用get from数据库委托。
但是,GetOrAdd最终将只使用委托返回的一个值,通过返回一个Lazy值,您可以保证只调用一个Lazy值。

您说每小时有一百万个缓存请求,但您创建新缓存的频率是多少?如果您只有5个缓存,并且平均每30分钟清除一次,那么您的锁定基本上不会占用任何开销。另一方面,如果每30秒填充10个缓存,那么锁定策略将增加大量开销。它还关系到您从未填充的不同缓存请求项目的频率。如果数量很多,你不应该这样做,如果不是,这不太可能成为瓶颈。@Servy百万新项目在一个小时内到达。因此,将建立一个锁,获取项目,并将其添加到缓存中。您说您每小时有一百万个缓存请求,但您创建新缓存的频率是多少?如果您只有5个缓存,并且平均每30分钟清除一次,那么您的锁定基本上不会占用任何开销。另一方面,如果每30秒填充10个缓存,那么锁定策略将增加大量开销。它还关系到您从未填充的不同缓存请求项目的频率。如果数量很多,你不应该这样做,如果不是,这不太可能成为瓶颈。@Servy百万新项目在一个小时内到达。因此,将建立锁、获取项并将其添加到缓存中。您现在不会使任何内容过期,并且可能会多次加载缓存。OP的代码没有任何问题。使用Lazy的技巧有问题,或者已经在其他实现中出现,因为如果PullFromDatabase返回异常,它会将异常保存在缓存中。是吗?@AngryHacker你说得对,我还没想过。我想解决这个问题的一种方法是捕获延迟委托中的异常,并在catch块中将缓存设置为null。然后,检查检查是否为null。如果是这样,请从缓存中删除该项,并在需要时引发异常。您现在不会使任何内容过期,并且可能会多次加载缓存。OP的代码没有任何问题。使用Lazy的技巧有问题,或者已经在其他实现中出现,因为如果PullFromDatabase返回异常,它会将异常保存在缓存中。是吗?@AngryHacker你说得对,我还没想过。我想解决这个问题的一种方法是捕获延迟委托中的异常,并在catch块中将缓存设置为null。然后,检查检查是否为null。如果是这样,请从缓存中删除该项,并在需要时引发异常。