【发布时间】:2015-08-09 23:24:23
【问题描述】:
这可能就像在棍子上要月亮一样;但是是否有一个“C# 生产质量的线程安全内存 LRU 缓存并过期?或者有没有人有一个最佳实践的想法来实现同样的事情?
(LRU 是“最近最少使用”-http://en.wikipedia.org/wiki/Cache_algorithms#LRU)
澄清一下:我想通过以下接口支持 ASP.Net MVC 站点中的内存缓存:
public interface ICache
{
T GetOrAdd<T>(string key, Func<T> create, TimeSpan timeToLive) where T : class;
bool Remove(string key);
}
- 我想要“GetOrAdd”,因为我希望这些操作是“原子的” - 即避免两个线程同时尝试查询缓存的竞争条件
- 我想要 Create 函数,因为创建这些对象的成本很高(需要复杂的数据库访问)
- 我需要过期,因为这些对象会在一段时间后过期
Microsoft 的最佳解决方案似乎是“System.Runtime.Caching.MemoryCache”,但它似乎带有一些警告:
- 它需要定期轮询缓存以遵守强加的内存限制。我不可能在我的系统中耗尽内存。我读过这篇让我担心的帖子:MemoryCache does not obey memory limits in configuration
- 它似乎只有“AddOrGetExisting”来支持我的接口,该接口将构造的对象作为第二个参数 - 如果创建此对象的成本很高,那么预先构造它是否会破坏缓存的点?
代码如下所示:
public sealed class Cache : ICache
{
private readonly MemoryCache _cache;
public Cache()
{
_cache = MemoryCache.Default;
}
public T GetOrAdd<T>(string key, Func<T> create, TimeSpan timeToLive) where T : class
{
// This call kinda defeats the point of the cache ?!?
var newValue = create();
return _cache.AddOrGetExisting(key, newValue, DateTimeOffset.UtcNow + timeToLive) as T;
}
public bool Remove(string key)
{
_cache.Remove(key);
return true;
}
}
或者也许在 Lazy 周围有更好的东西,它确实只允许创建一次结果,但感觉就像一个 hack(缓存 Func 有什么后果吗?):
class Program
{
static void Main(string[] args)
{
Func<Foo> creation = () =>
{
// Some expensive thing
return new Foo();
};
Cache cache = new Cache();
// Result 1 and 2 are correctly the same instance. Result 3 is correctly a new instance...
var result1 = cache.GetOrAdd("myKey", creation, TimeSpan.FromMinutes(30));
var result2 = cache.GetOrAdd("myKey", creation, TimeSpan.FromMinutes(30));
var result3 = cache.GetOrAdd("myKey3", creation, TimeSpan.FromMinutes(30));
return;
}
}
public sealed class Foo
{
private static int Counter = 0;
private int Index = 0;
public Foo()
{
Index = ++Counter;
}
}
public sealed class Cache
{
private readonly MemoryCache _cache;
public Cache()
{
_cache = MemoryCache.Default;
}
public T GetOrAdd<T>(string key, Func<T> create, TimeSpan timeToLive) where T : class
{
var newValue = new Lazy<T>(create, LazyThreadSafetyMode.PublicationOnly);
var value = (Lazy<T>)_cache.AddOrGetExisting(key, newValue, DateTimeOffset.UtcNow + timeToLive);
return (value ?? newValue).Value;
}
public bool Remove(string key)
{
_cache.Remove(key);
return true;
}
}
其他想法:
- 我也找到了这个实现,但它不允许您指定基于时间的到期:Is it there any LRU implementation of IDictionary?
- 也许有一个使用 ReaderWriterLock 的实现?
- ConcurrentDictionary 的一些包装器?
【问题讨论】:
-
试试this
-
该项目已弃用。您指的是 CSharpTest.Net.Collections 的 LurchTable 吗? github.com/csharptest/CSharpTest.Net.Collections - 不支持 Expiry ...
-
奇怪的是没有一个好的LRU缓存项目。我可能不得不自己开始一个:)
-
您的
GetOrAdd方法与MemoryCache上可用的Get和Set方法实现起来相当简单——您不必使用AddOrGetExisting。至于尊重内存限制,在 .NET 中严格意义上实现它实际上是不可能的,但除非您的系统上没有配置虚拟内存(这是一个坏主意,我怀疑是这种情况),否则实际上缓存会一旦达到高内存使用率,就会非常快速地驱逐项目,并且永远不会导致您出现内存不足错误。
标签: c# asp.net-mvc multithreading collections lru