如何不删除其他对象引用的缓存项

时间:2012-11-13 13:36:49

标签: .net design-patterns caching memory-management

我假设一个对象代表某个东西,你只能在内存中有一个实例。我避免重复和等于对象。

  1. 假设一个对象由City类型的“纽约”唯一标识,包含在缓存中(扩展System.Runtime.Caching.ObjectCache)。此对象由另一个名为MyBusinessObject

  2. 的对象引用
  3. 缓存删除“纽约”对象,但对象MyBusinessObject仍然引用“纽约”。垃圾收集器不会从内存中删除此对象,因为它仍然具有引用。

  4. 另一个对象从缓存中请求“纽约”。缓存为“纽约”

  5. 加载City的新实例

    现在有两个“纽约”实例,一个MyBusinessObject引用的实例(无效)和缓存引用的新“纽约”实例。

    是否有设计模式可以解决此问题?我不希望MyBusinessObject使用City的陈旧实例。

    一种可能的解决方案不会消除被引用的缓存对象,但是,如何做到这一点?

    以下是我正在尝试解释的UML图:

    Class diagram UML

    Objects diagram

1 个答案:

答案 0 :(得分:2)

在这种情况下,您的缓存不应该缓存实际的对象,而是缓存正在缓存的对象的包装器,它还包含有关缓存中对象状态的信息。

例如,您可以拥有一个类似的简单类,表示缓存中的项目:

public class CacheItem<T>
{
    // Since the cache is the only thing
    // that should be making CacheItems,
    // make this internal to the assembly
    // that the cache is implemented in.
    // This constructor is called before
    // an add.
    internal CacheItem(T item)
    {
        // Set the property values.
        Item = item;
    }

    // Poor-man's immutability.
    public T Item { get; private set; }

    // The backing field for IsCached, it
    // is volatile so that it can serialize
    // access for single reads/writes, which is
    // what the property does.
    // Assume it is being added when constructed.
    private volatile bool _isCached = true;

    // Only able to be set by the cache.
    // The setter is set to false when the item
    // is stale (not in the cache any longer).
    public bool IsCached 
    {
        get { return _isCached; }
        set { _isCached = value; } 
    }
}

这里的想法很简单:

  • 当您的缓存即将在缓存中输入项目的新实例时,它会调用构造函数(构造函数应可用于缓存,如有必要,您可以使CacheItem具有私有构造函数而不是内部的嵌套类,它将IsCached属性值设置为true。然后将该项目放入缓存中。

  • 当项目从缓存过期时,缓存会将IsCached属性设置为false(同样,此属性只能由缓存访问)。

  • 责任移至客户端以检查CacheItem<T>是否过时。

请注意,这是一个拉动操作。如果需要,可以通过向CacheItem<T>添加事件来添加推送操作支持,如下所示:

public class CacheItem<T>
{
    // Since the cache is the only thing
    // that should be making CacheItems,
    // make this internal to the assembly
    // that the cache is implemented in.
    // This constructor is called before
    // an add.
    internal CacheItem(T item)
    {
        // Set the property values.
        Item = item;
    }

    // Poor-man's immutability.
    public T Item { get; private set; }

    // The lock for the event registrations.
    // Since everything else is immutable, this needs
    // to be as well.
    private readonly object _eventLock = new object();

    // The backing field for the Expired
    // event.  Since everything else is immutable
    // this needs to be as well.
    private readonly EventHandler _expiredHandlers;

    // The expires event.
    public event EventHandler Expired
    {
        add { lock (_eventLock) _expiredHandlers += value; }
        remove { lock (_eventLock) _expiredHandlers -= value; }
    }

    // The backing field for IsCached, it
    // is volatile so that it can serialize
    // access for single reads/writes, which is
    // what the property does.
    // Assume it is being added when constructed.
    private volatile bool _isCached = true;        

    // The setter is set to false by the 
    // Expire method (called by the cached)
    // when the item is stale 
    // (not in the cache any longer).
    public bool IsCached { get { return _isCached; } }

    // Called internally by the cache.
    internal void Expire()
    {
        // Set _isCached to false.
        _isCached = false;

        // Get the event handlers and fire
        // the event.  Getting the handlers
        // needs to be synchronized.
        EventHandler handlers;

        // Synchronize.
        lock (_eventLock) handlers = _expiredHandlers;

        // Fire if there are handlers.
        if (handlers != null) handlers(this, EventArgs.Empty);
    }
}

现在,您可以让您的客户订阅Expired事件,该事件将在缓存项无效时向客户端指示。通过在缓存中删除项目时调用项目上的内部Expire事件,缓存会触发此操作(并且它还会将IsCached属性设置为false)。