我试图找出应该如何使用MemoryCache以避免内存异常。我来自ASP.Net背景,其中缓存管理它自己的内存使用,所以我希望MemoryCache也会这样做。这似乎不是我所做的波纹管测试程序中所示的情况:
class Program
{
static void Main(string[] args)
{
var cache = new MemoryCache("Cache");
for (int i = 0; i < 100000; i++)
{
AddToCache(cache, i);
}
Console.ReadLine();
}
private static void AddToCache(MemoryCache cache, int i)
{
var key = "File:" + i;
var contents = System.IO.File.ReadAllBytes("File.txt");
var policy = new CacheItemPolicy
{
SlidingExpiration = TimeSpan.FromHours(12)
};
policy.ChangeMonitors.Add(
new HostFileChangeMonitor(
new[] { Path.GetFullPath("File.txt") }
.ToList()));
cache.Add(key, contents, policy);
Console.Clear();
Console.Write(i);
}
}
在大约达到2GB的内存使用量(任何CPU)或消耗了我所有机器的物理内存(x64)(16GB)之后,上面引发了内存不足异常。
如果我删除了cache.Add位,程序不会抛出任何异常。如果我在每次缓存添加后都包含对cache.Trim(5)的调用,我会看到它释放了一些内存,并且它在任何给定时间(从cache.GetCount())在缓存中保留了大约150个对象。
正在调用cache.Trim我的程序是否有责任?如果是这样的话什么时候应该被调用(比如我的程序怎么知道内存已经满了)?你如何计算百分比参数?
注意:我计划在长时间运行的Windows服务中使用MemoryCache,因此对其进行适当的内存管理至关重要。
答案 0 :(得分:1)
MemoryCache具有一个后台线程,该线程定期估计进程正在使用多少内存以及缓存中有多少键。当它认为您接近cachememorylimit时,它将修剪缓存。每次此后台线程运行时,它都会检查您是否接近极限,并在内存压力下增加轮询频率。
如果您非常快速地添加项目,则后台线程将没有运行的机会,并且在缓存可以修剪和GC可以运行之前,您可能会耗尽内存(在x64进程中,这可能会导致大量堆大小)和GC分钟暂停)。修剪过程/内存估计还具有bugs under some conditions。
如果由于快速加载过多的对象而导致程序容易出现内存不足的情况,则使用LRU高速缓存之类的有限大小的对象是一种更好的策略。 LRU通常使用基于项目数的策略来逐出最近最少使用的项目。
我写了一个线程安全的TLRU实现(一种时间紧迫的最近最少使用的策略),您可以轻松地用它代替ConcurrentDictionary。
可在Github上找到:https://github.com/bitfaster/BitFaster.Caching
Install-Package BitFaster.Caching
在您的程序中使用它看起来像这样,并且不会耗尽内存(取决于文件的大小):
class Program
{
static void Main(string[] args)
{
int capacity = 80;
TimeSpan timeToLive = TimeSpan.FromMinutes(5);
var lru = new ConcurrentTLru<int, byte[]>(capacity, timeToLive);
for (int i = 0; i < 100000; i++)
{
var value = lru.GetOrAdd(1, (k) => System.IO.File.ReadAllBytes("File.txt"));
}
Console.ReadLine();
}
}
如果您确实要避免耗尽内存,还应该考虑将文件读入RecyclableMemoryStream,并使用BitFaster中的Scoped类使缓存的值成为线程安全的,并避免在处置时出现争执。 / p>