C#:如何实现智能缓存

时间:2009-07-15 13:10:08

标签: c# performance caching resources memory-management

我有一些地方可以实现某种缓存。例如,在基于自定义字符串执行资源查找,使用反射查找属性名称或每个属性名称只有一个PropertyChangedEventArgs的情况下。

最后一个简单的例子:

public static class Cache
{
    private static Dictionary<string, PropertyChangedEventArgs> cache;
    static Cache()
    {
        cache = new Dictionary<string, PropertyChangedEventArgs>();
    }
    public static PropertyChangedEventArgs GetPropertyChangedEventArgs(
        string propertyName)
    {
        if (cache.ContainsKey(propertyName))
            return cache[propertyName];

        return cache[propertyName] = new PropertyChangedEventArgs(propertyName);
    }
}

但是,这会运作良好吗?例如,如果我们有一大堆不同的propertyNames,那就意味着我们最终会有一个巨大的缓存,从来没有被垃圾收集或任何东西。我在想如果缓存的内容是更大的值,如果应用程序是一个长期运行的应用程序,这可能最终会成为一个问题......或者你怎么看?如何实现好的缓存?这个对大多数用途来说是否足够好?一些很好的缓存实现的例子,这些实现不是很难理解,也不太复杂而无法实现?

6 个答案:

答案 0 :(得分:25)

这是一个大问题,您需要确定问题的域并应用正确的技术。例如,您如何描述对象的到期?它们会在固定的时间间隔内变得陈旧吗?它们是否因外部事件而变得陈旧?这种情况多久发生一次?另外,你有多少个物品?最后,生成对象需要多少钱?

最简单的策略是直接进行记忆,如上所述。这假设对象永远不会过期,并且没有太多的内存干,你认为创建这些对象的成本保证使用缓存开始。

下一层可能是限制对象的数量,并使用隐式过期策略,例如LRU(最近最少使用)。要做到这一点,除了字典之外,通常还会使用双向链表,每次访问对象时,它都会移到列表的前面。然后,如果您需要添加一个新对象,但它超出了总对象的限制,您将从列表的后面删除。

接下来,您可能需要根据时间或某些外部刺激强制显式到期。这将要求您具有可以调用的某种到期事件。

正如您所看到的,缓存中有很多设计,因此您需要正确理解您的域和工程师。我觉得你没有提供足够的细节来讨论具体细节。

P.S。在定义类时请考虑使用泛型,以便可以存储许多类型的对象,从而允许重用缓存代码。

答案 1 :(得分:18)

您可以将每个缓存的项目包装在WeakReference中。这将允许GC在需要时回收项目,但是它不会对项目何时从缓存中消失进行任何细粒度控制,或者允许您实现明确的过期策略等。

(哈!我刚注意到MSDN page上给出的示例是一个简单的缓存类。)

答案 2 :(得分:9)

看起来.NET 4.0现在支持System.Runtime.Caching来缓存许多类型的东西。你应该先研究一下,而不是重新发明轮子。更多细节:

http://msdn.microsoft.com/en-us/library/system.runtime.caching%28VS.100%29.aspx

答案 3 :(得分:3)

这是一个很好的辩论,但根据你的应用,这里有一些提示:

您应该定义缓存的最大大小,如果缓存已满,如何处理旧项目,具有清理策略,确定缓存中对象的生存时间,缓存是否必须/必须保留在应用程序异常终止的情况下,其他地方的内存......

答案 4 :(得分:3)

这是一个常见问题,根据您的应用需求,有许多解决方案。 微软发布一个完整的库来解决它是如此常见。 在汇总自己的缓存之前,您应该查看Microsoft Velocity。 http://msdn.microsoft.com/en-us/data/cc655792.aspx 希望这有帮助。

答案 5 :(得分:1)

你可以使用WeakReference但是如果你的对象不是那么大,因为WeakReference会占用比对象本身更多的内存,这不是一个好技术。此外,如果对象是短时间使用,它将永远不会从GC的第0代进入第1代,则WeakReference不需要SuppressFinalize,但对象上的IDisposable接口将具有该版本在{{1}}。

如果你想控制生命周期,你需要一个计时器来再次更新日期时间/时间跨度缓存中对象上的desiredExpirationTime。

重要的是,如果对象很大,那么选择WeakReference,否则使用强引用。此外,您可以在Dictionary上设置容量,并创建一个队列,用于请求临时箱中的其他对象序列化对象,并在“词典”中有空间时加载它,然后从临时目录中清除它。