【问题标题】:How to manage IDisposable Objects that are cached?如何管理缓存的 IDisposable 对象?
【发布时间】:2009-02-20 11:46:24
【问题描述】:

我有一个创建成本很高的对象,它使用一些非托管资源,这些资源在完成后必须显式释放,因此实现 IDisposable()。我想要一个缓存这些昂贵资源的实例,以便最大限度地降低创建成本,但我不知道如何处理处置。

如果使用对象的方法负责处置,那么我最终会在缓存中处置实例,然后必须重新创建这些实例,从而破坏缓存点。如果我不在使用它们的方法中处置对象,那么它们永远不会被处置。我认为当它们从缓存中取出时我可以处理它们,但是我最终可能会处理一个仍在被方法使用的实例。

让它们超出范围并被垃圾收集器收集并在那时释放资源是否有效?这感觉是错误的,并且反对它们是一次性的......

【问题讨论】:

    标签: .net caching idisposable


    【解决方案1】:

    一次性物品总是需要有一个明确的所有者负责处理它们。但是,这并不总是创建它们的对象。此外,所有权可以转让。

    意识到这一点,解决方案就变得显而易见了。 不要丢弃,回收! 你不仅需要一种从缓存中获取资源的方法,还需要一种返回它的方法。此时缓存再次成为所有者,并且可以选择保留资源以供将来使用或处置。

       public interface IDisposableItemCache<T> : IDisposable
          where T:IDisposable 
       {
          /// <summary>
          /// Creates a new item, or fetches an available item from the cache.
          /// </summary>
          /// <remarks>
          /// Ownership of the item is transfered from the cache to the client.
          /// The client is responsible for either disposing it at some point,
          /// or transferring ownership back to the cache with
          /// <see cref="Recycle"/>.
          /// </remarks>
          T AcquireItem();
    
          /// <summary>
          /// Transfers ownership of the item back to the cache.
          /// </summary>
          void Recycle(T item);
    
       }
    

    编辑:我刚刚注意到这个想法也存在于 Spring 中,它被称为 object pool。他们的BorrowObjectReturnObject 方法与我的示例中的方法相匹配。

    【讨论】:

    • 我基本上最终得到了一个混合了这个和 NoBugz 答案的解决方案。谢谢
    【解决方案2】:

    致(错误)引用 Raymond Chen:没有过期策略的每个缓存都是泄漏

    所以,设置一个明确的缓存过期策略,让缓存按正常情况处理它们。这仍然需要处理应用程序关闭。

    如果您的非托管资源归进程所有,您可以让进程在关闭时释放它们。

    如果非托管资源归进程所有,则需要检测关闭并明确 Dispose 缓存的元素。

    如果你不能可靠地检测到进程关闭,并且托管资源很昂贵,那么非托管资源不会,将托管资源与非托管资源分开,让缓存只保留托管资源。

    如果非托管资源很昂贵,因此需要缓存,并且它们不属于进程,并且您无法可靠地检测到进程关闭并且无法承受泄漏它们,那么您的问题就无法解决。

    【讨论】:

      【解决方案3】:

      首先,包装原生资源的类型应该是可终结的,而不仅仅是一次性的。更好的是,使用SafeHandle 包装原生资源。

      除非有人明确负责说他们已经完成了该项目并且可以处置它,否则我认为您最好让 GC 来处理它。请注意,它必须是可终结的,否则 GC 不会再看一眼。

      【讨论】:

        【解决方案4】:

        您可以将非托管资源与托管实例分离,并使用缓存管理器来保存一组非托管资源。托管对象将尝试从缓存管理器获取非托管资源的实例,该实例将从缓存中创建一个或提供一个空闲实例,并在处理时将其返回给缓存管理器(而不是自行处理) .缓存管理器将是唯一负责分配和释放非托管资源的对象。

        【讨论】:

          【解决方案5】:

          您可以使用类工厂和 IDisposable 解决此问题。例如:

          public class CachedObject : IDisposable {
            private int mRefCount;
            private CachedObject(int something) {
              mRefCount = 1;
            }
            public static CachedObject CreateObject(int something) {
              CachedObject obj = LookupInCache(something);
              if (obj != null) Interlocked.Increment(ref obj.mRefCount);
              else obj = new CachedObject(something);
              return obj;
            }
            private static CachedObject LookupInCache(int something) {
              CachedObject obj = null;
              // Implement cache lookup here, don't forget to lock
              //..
              return obj;
            }
            public void Dispose() {
              int cnt = Interlocked.Decrement(ref mRefCount);
              if (cnt == 0) {
                // Remove from cache
                // 
              }
            }
          }
          

          【讨论】:

          • 此解决方案可能会产生一些意想不到的效果,因为它歪曲了所有权的概念。客户端代码负责调用 dispose,但如果对象是可变的,它还需要注意可能有其他所有者在使用同一个对象。
          • 不,如果它是可变的,那么它不应该被缓存。
          • 这个类不是线程安全的——你的联锁 inc/dec 不保护 if 守卫。
          • (基本上就是用锁代替...)
          【解决方案6】:

          一个对象应该由创建它的类来处理。由于您的调用者尚未在缓存中创建项目,因此他们也无权处置它们。

          我想确保你的工厂方法被命名为“GetClass”而不是“CreateClass”,以强调调用者是不负责创建,因此不负责处置。

          【讨论】:

          • “创造者是所有者”是一个可能的设计准则,但正如您的回答已经表明的那样:并不总是清楚谁是创造者(工厂或工厂客户?)。最好清楚明确地记录这一点。正如我自己的回答所示,您甚至可以转让所有权。
          猜你喜欢
          • 1970-01-01
          • 2010-09-20
          • 2012-06-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-10
          • 1970-01-01
          相关资源
          最近更新 更多