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 {
  . . .
  /// 
  /// 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.
  /// 
  public Slot(IObjectFactory<T> objectFactory) {
    if (objectFactory == null)
      throw new ArgumentNullException("objectFactory");
    this.objectFactory = objectFactory;
  }

  IObjectFactory<T> objectFactory;
  T poolObject;

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

class Pool {
  . . .
  /// 
  /// 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.
  /// 
  public Pool(IObjectFactory<T> objectFactory, int initialPoolSize) : this( ) {
    this.objectFactory = objectFactory;
    if (initialPoolSize > 0)
      ExtendPoolBy(initialPoolSize);
  }

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

  private bool delayObjectCreation;
  /// 
  /// 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.
  /// 
  public bool DelayObjectCreation {
    get {
      return delayObjectCreation;
    }
    set {
      if (delayObjectCreation != value) {
        delayObjectCreation = value;
      }
    }
  }

  /// 
  /// Extends the number of slots in the pool by the number given in
  /// the count parameter. 
  /// 
  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.

5 Comments on Object pooling, part 4

  1. Interesting set of articles. I created an object pool a while back (about 9 months ago) for .NET 2.0. Architecturally, we have very similar approaches (except naming conventions). But one problem I haven’t solved yet (admittedly, because I haven’t tried), is making it work in ASP.NET where it actually counts for all of my scenarios.When I put a pool in the Application (or Cache) space in ASP.NET, each page is contending for an object from the pool, but will execute the object on the same thread as the Application/Cache space (which is not the same thread as the page). Needless to say, performance wasn’t all that rockin’.The only way I know of to solve that is that have a custom thread pool (so as not to starve ASP.NET by using the default ThreadPool) and have each instance reside on its own thread. What’s your take?Thanks,Shawn

    Like

  2. I’m not specifically an expert on ASP.NET, so maybe I’m missing something. But I don’t see why you say “… but will execute the object on the same thread as the Application/Cache space …”. The way I see it, you’ll need one instance of the pool, for which the application/cache space is probably a good place. But I wouldn’t say that this pool object runs on a particular thread, because it doesn’t really do anything on its own at all, does it? It just sits there, waiting for requests.Now, when a request comes in in the form of a call to GetObject(), the code in the GetObject() method is executed in the thread of the caller, which is why I protect access to the management structures from that code with a lock. Finally, when an object is returned to the calling object by the GetObject() method, the caller will presumably “use” that object for whatever it’s intended to be used for. Any executions of code in the pooled object will then occur in the thread that the caller is running in, be that a Page’s thread or whatever else. If you look at the origin of the objects in the pool, you might find that they were all created on very different threads – the first few might have been created when the pool was created, any number of others were possibly created when the pool ran dry within a GetObject() call, so this would have happened on the thread of the caller that just happened to call at that moment. But an object is just a piece of data, it doesn’t “belong” to a specific thread, so there shouldn’t be a problem as long as the object is only ever used by one caller at a time – something that the pool should guarantee, as well as it can.So, maybe these explanations help you? If they don’t, maybe I’m missing something that’s specific to ASP.NET – or could you explain the problem you’re seeing in more detail?

    Like

  3. The way my ObjectPool works… is it acts as a generic list like yours, but I derive a strongly typed pool, where InvoiceObjectPool : ObjectPool something like that.Anyway, I have a method “GetInstance()” that returns an instance of type T (in List) (in this case, Invoice). These are heavy objects.So… InvoiceObjectPool lives in Application[] of ASP.NET. When my page request says:Invoice = InvoiceObjectPool.GetInstance();it is actually creating or reusing an instance that was created and resides on the same thread that Application[] resides in.That said, all of the functionality that Invoice respresents, is actually being executed on the main thread, not the thread servicing the Request. Thus, it is a bottleneck in a high load environment.Using a ReaderWriterLock (or the lock() in your case) on the object pool assists with locking issues for creating/removing items from the pool (in ASP.NET) but not which thread the object in the pool executes in (it is not actually executing on the working/Request thread).Hope that clarifies.Thanks,Shawn

    Like

  4. I hope I’m correct with my assessment on ASP.NET threading issues… its been a while, I’ll go double-check when I get home tonight.Thanks,Shawn

    Like

  5. I still don’t get why “the functionality that Invoice represents” should be executed on the main thread. Who starts that code in the main thread? There’s got to be a caller, hasn’t there? When the call to that functionality comes from the request thread, why would the code be executed on the main thread?Even if the Invoice object had some functionality where code is being executed without a caller, that would mean the Invoice object would have to run that code itself. When does it do that, in its constructor? Maybe that’s what you mean… in that case you should simply make the Invoice object start its own thread for its own code, so that it doesn’t block the thread in which it is constructed – but that’s still not the main thread most of the time; it should be the thread where the new object gets constructed, which would depend on the growing mechanism(s) you have in your pool.

    Like

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s