10

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

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

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

4

6 回答 6

4

To (mis-)quote Raymond Chen:每个没有过期策略的缓存都是泄漏

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

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

如果进程不拥有非托管资源,则需要检测关闭并显式处理缓存的元素。

如果您无法可靠地检测到进程关闭,并且托管资源很昂贵,则非托管资源不会,请将托管资源与非托管资源分开,并让缓存仅保留托管资源。

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

于 2009-02-20T12:14:34.410 回答
4

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

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

   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方法与我的示例中的方法相匹配。

于 2009-02-20T17:21:07.293 回答
3

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

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

于 2009-02-20T11:51:46.243 回答
2

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

于 2009-02-20T11:51:29.830 回答
1

您可以使用类工厂和 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
      // 
    }
  }
}
于 2009-02-20T13:31:28.997 回答
0

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

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

于 2009-02-20T13:18:30.167 回答