Object Pooling, Part 4

This is the fourth article in my mini series about object pooling. Be sure to read part 1, part 2 and part 3 first. This part will be about automated object creation. To begin with, we need a way for the object pool to have additional objects created at all. One way to do that would be to just call new on the object type – and to use the new constraint on the generic class definition, of course. But that’s an unnecessary restriction, and it would imply that poolable classes always need to have a default constructor. So I decided to go a different way and just define an interface that gets passed in to the Pool class and that can be used when new objects need to be created. This is it:

public interface IObjectFactory<T> {
  T CreateObject( );
}

Now when will the Pool actually have the necessity of creating new objects? This can happen in at least two situations:

  1. When there are not enough free objects left in the pool. I’ll see later how this decision is reached.
  2. When the Pool instance is created and there are no objects in the pool at all.

An additional measure of flexibility that I’d like to include is to make it an option to defer the creation of a poolable object to the point where the object is actually being accessed via the Slot. For pooling situations where the pool size may grow or shrink very rapidly, this could be helpful to distribute load over time, instead of introducing great load peaks when the pool needs to grow. Here are the changes I made to the classes:

class Slot {
  // ...
  /// <summary>
  /// Constructs a Slot object without a pre-existing pooled type object.
  /// Creation of the pooled type object will be deferred to the point
  /// where the object is actually accessed via the PoolObject property.
  /// </summary>
  public Slot(IObjectFactory<T> objectFactory) {
    if (objectFactory == null)
      throw new ArgumentNullException("objectFactory");
    this.objectFactory = objectFactory;
  }

  IObjectFactory<T> objectFactory;
  T poolObject;

  /// <summary>
  /// Returns the pooled object itself. If there isn't any object yet,
  /// it'll be created on the fly using the object factory.
  /// </summary>
  public T PoolObject {
    get {
      if (poolObject == null)
        poolObject = objectFactory.CreateObject( );
      return poolObject;
    }
  }
  // ...
}

class Pool {
  // ...
  /// <summary>
  /// Creates a new Pool object. The list of objects in the pool is
  /// expanded by a number of additional slots, as given by the
  /// initialPoolSize parameter. Objects are created by a call to the
  /// IObjectFactory<T>.CreateObject() method.
  /// </summary>
  public Pool(IObjectFactory<T> objectFactory, int initialPoolSize) : this( ) {
    this.objectFactory = objectFactory;
    if (initialPoolSize > 0)
      ExtendPoolBy(initialPoolSize);
  }

  private IObjectFactory<T> objectFactory;
  /// <summary>
  /// Gets or sets a value defining the object factory that can be used
  /// by the pool to create new poolable objects as necessary.
  /// </summary>
  public IObjectFactory<T> ObjectFactory {
    get {
      return objectFactory;
    }
    set {
      if (objectFactory != value) {
        objectFactory = value;
      }
    }
  }

  private bool delayObjectCreation;
  /// <summary>
  /// Gets or sets a flag that defines whether a poolable type object is
  /// immediately created together with a new Slot, or whether the
  /// creation of the poolable type object will be delayed.
  /// </summary>
  public bool DelayObjectCreation {
    get {
      return delayObjectCreation;
    }
    set {
      if (delayObjectCreation != value) {
        delayObjectCreation = value;
      }
    }
  }

  /// <summary>
  /// Extends the number of slots in the pool by the number given in
  /// the count parameter.
  /// </summary>
  public void ExtendPoolBy(int count) {
    if (objectFactory == null)
      throw new InvalidOperationException("An ObjectFactory must be configured.");
    if (count <= 0)
      throw new ArgumentOutOfRangeException("Count must be greater than zero.");
    lock (poolLock) {
      count = Math.Min(maxPoolSize - pool.Count, count);

      for (int i = 0; i < count; i++) {
        Slot slot = delayObjectCreation ? new Slot(objectFactory) :
          new Slot(objectFactory.CreateObject( ));
        pool.Add(slot);
      }
    }
  }
  // ...
}

That’ll be it for now. The next thing I’ll look at is the right place to grow and shrink the pool size.

Sorry, this blog does not support comments.

I used various blog hosting services since this blog was established in 2005, but unfortunately they turned out to be unreliable in the long term and comment threads were lost in unavoidable transitions. At this time I don't want to enable third-party services for comments since it has become obvious in recent years that these providers invariably monetize information about their visitors and users.

Please use the links in the page footer to get in touch with me. I'm available for conversations on Keybase, Matrix, Mastodon or Twitter, as well as via email.