C#的对象缓存

我正在为一些文档格式做文档查看器。 为了方便起见,我们假设这是一个PDF查看器,一个桌面应用程序 。 该软件的一个要求是渲染速度。 所以,现在,当用户滚动文档时,我正在为下一页缓存图像。

这是有效的,UI非常敏感,似乎应用程序几乎可以立即呈现页面……但代价是:内存使用量有时会达到600MB。 我将它全部缓存在内存中。

现在,我知道,我可以缓存到磁盘,但是一直这样做会明显变慢。 我想要做的是实现一些缓存(LRU?),其中一些缓存页面(图像对象)在内存上,大多数都在磁盘上。

在我开始这个之前,框架中有什么东西或某些库可以为我做这个吗? 这似乎是一个非常普遍的问题。 (这是一个桌面应用程序,而不是ASP.NET)

或者,您对此问题有其他想法吗?

我写了一个LRU Cache和一些测试用例,随意使用它。

您可以在我的博客上阅读来源。

对于懒惰(这里是减去测试用例):

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace LRUCache { public class IndexedLinkedList { LinkedList data = new LinkedList(); Dictionary> index = new Dictionary>(); public void Add(T value) { index[value] = data.AddLast(value); } public void RemoveFirst() { index.Remove(data.First.Value); data.RemoveFirst(); } public void Remove(T value) { LinkedListNode node; if (index.TryGetValue(value, out node)) { data.Remove(node); index.Remove(value); } } public int Count { get { return data.Count; } } public void Clear() { data.Clear(); index.Clear(); } public T First { get { return data.First.Value; } } } } 

LRUCache

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace LRUCache { public class LRUCache : IDictionary { object sync = new object(); Dictionary data; IndexedLinkedList lruList = new IndexedLinkedList(); ICollection> dataAsCollection; int capacity; public LRUCache(int capacity) { if (capacity <= 0) { throw new ArgumentException("capacity should always be bigger than 0"); } data = new Dictionary(capacity); dataAsCollection = data; this.capacity = capacity; } public void Add(TKey key, TValue value) { if (!ContainsKey(key)) { this[key] = value; } else { throw new ArgumentException("An attempt was made to insert a duplicate key in the cache."); } } public bool ContainsKey(TKey key) { return data.ContainsKey(key); } public ICollection Keys { get { return data.Keys; } } public bool Remove(TKey key) { bool existed = data.Remove(key); lruList.Remove(key); return existed; } public bool TryGetValue(TKey key, out TValue value) { return data.TryGetValue(key, out value); } public ICollection Values { get { return data.Values; } } public TValue this[TKey key] { get { var value = data[key]; lruList.Remove(key); lruList.Add(key); return value; } set { data[key] = value; lruList.Remove(key); lruList.Add(key); if (data.Count > capacity) { data.Remove(lruList.First); lruList.RemoveFirst(); } } } public void Add(KeyValuePair item) { Add(item.Key, item.Value); } public void Clear() { data.Clear(); lruList.Clear(); } public bool Contains(KeyValuePair item) { return dataAsCollection.Contains(item); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { dataAsCollection.CopyTo(array, arrayIndex); } public int Count { get { return data.Count; } } public bool IsReadOnly { get { return false; } } public bool Remove(KeyValuePair item) { bool removed = dataAsCollection.Remove(item); if (removed) { lruList.Remove(item.Key); } return removed; } public IEnumerator> GetEnumerator() { return dataAsCollection.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((System.Collections.IEnumerable)data).GetEnumerator(); } } } 

对于.NET 4.0,您还可以使用System.Runtime.CachingMemoryCache

http://msdn.microsoft.com/en-us/library/system.runtime.caching.aspx

有模式和实践企业库 (更具体地说, 缓存应用程序块 ),但IMO往往过度设计和过于复杂。

.NET Framework始终能够保持对对象的弱引用 。

基本上,弱引用是对运行时认为“不重要”的对象的引用,并且可以在任何时间点通过垃圾回收运行来删除。 例如,这可以用于缓存事物,但是你无法控制被收集的东西和不收集的东西。

另一方面,它使用起来非常简单,它可能就是您所需要的。

戴夫

经典的权衡情况。 将所有内容保存在内存中会以大量增加的内存消耗为代价,而从光盘中检索可以减少内存消耗,但效果不是很好。 但是,你已经知道了这一切!

内置的System.Web.Caching.Cache类很棒,我自己在ASP.NET应用程序中使用它很多次(虽然主要用于数据库记录缓存)但是,缺点是缓存只能在一台计算机上运行(通常是唯一的Web服务器),并且不能分布在多台计算机上。

如果有可能“抛出一些硬件”问题,并且它不一定需要昂贵的硬件,只需要有大量内存的盒子,你总是可以使用分布式缓存解决方案。 这将为您提供更多的内存,同时保持(几乎)相同的性能水平。

.NET的分布式缓存解决方案的一些选项是:

Memcached.NET

indeXus.Net

甚至是微软自己的Velocity项目。

你是如何实现缓存的?

您可以使用System.Web.CachingCache类 ,即使在非Web应用程序中也是如此,如果需要内存,它将在LRU的基础上清除项目。

在非Web应用程序中,您需要使用HttpRuntime.Cache来访问Cache实例。

请注意,文档声明Cache类不打算在ASP.NET之外使用,尽管它总是适用于我。 (但我从来没有在任何关键任务应用程序中依赖它。)

然而,高速缓存应用程序块和ASP.NET高速缓存都是选项,尽管它们执行LRU,但发生的唯一磁盘利用率是内存分页。 我认为有一些方法可以优化这些更具体的目标,以获得更好的输出。 以下是一些想法:

  • 由于它是一个ASP.NET应用程序,为什么不生成图像,将它们写入磁盘,当浏览器请求下一页时,IIS会为其提供服务。 这可以让你的ASP.NET工作进程更低,同时让IIS做它擅长的事情。
  • 使用有关用户如何交互的统计信息。 在ASP.NET缓存中实现的LRU通常适用于单个图像文件 – 通过它的声音对此场景没有多大用处。 相反,也许有些逻辑如下:“如果用户在最后Y秒内滚动了X页,那么一般是下一个X * Y图像”。 用户快速滚动将生成更多页面; 阅读缓慢的用户需要较少的缓存。
  • 让IIS从磁盘提供映像,并为您真正想要控制缓存的映像使用自定义HTTP处理程序。
  • 让浏览器提前请求文件,并依赖浏览器缓存。
  • 一旦图像被提供给客户端,可以说它几乎可以从缓存中删除吗? 这可以大大减少占地面积。

我当然会避免使用普通的哈希表。

除了rsbarro使用MemoryCache的答案之外 ,我建议使用PostSharp AOP,如中所述

http://www.codeproject.com/Articles/493971/Leveraging-MemoryCache-and-AOP-for-expensive-calls

有一个高效的开源RAM虚拟器,它使用MRU算法将最新引用的对象保留在内存中,并使用快速,轻量级的后备存储(在磁盘上)进行“分页”。

以下是Code Project中有关它的迷你文章的链接: http : //www.codeproject.com/Tips/827339/Virtual-Cache

希望对你有帮助。