Click here to Skip to main content
15,868,087 members
Articles / Programming Languages / C++/CLI
Article

ObjectPool - a library for pooling .NET objects

Rate me:
Please Sign up or sign in to vote.
4.79/5 (18 votes)
27 Oct 200216 min read 136.9K   2.4K   69   12
A (managed C++) library for object pooling in .NET

In this article

  1. Introduction
  2. Object what?
    1. Reusing objects (not classes)
    2. History (or about COM+ pooling)
    3. .NET pooling with ObjectPool
  3. ObjectPool library in a nutshell
    1. I am an IPoolableObject...
    2. ... so mark me with the PoolableAttribute
    3. ... and instantiate me via the ObjectPool
  4. Using the library (properly?)
    1. [Poolable] needs to be smart?
    2. Creating the pool
    3. Drawing objects from the pool
    4. Returning objects to the pool
    5. Destroying the pool
  5. Compiling ObjectPool
  6. TODO(s)
  7. Conclusion
  8. Reporting bugs
  9. Disclaimer

Introduction

This article demonstrates how useful could be the .NET custom attribute classes and shows how to build your own pool of reusable objects, which resembles the one in COM+. You're expected to at least have heard about (.NET) attributed programming/custom attributes, and a bit of Managed C++ and a very little bit about reflection :) But if you don't -- don't worry! I don't understand Java! :)

Object what?

Object pooling. If you have ever written a poolable MTS/COM+ component, you can skip this funny intro and go directly to read ObjectPool library in a nutshell. Otherwise keep reading... What is a pool? A container, full with water, where fish swim. In our case, an object pool is a container, where objects "swim" :) No, seriously, the object pool is a container, which not only allows objects to be drawn from, and returned back, but also creates them on the fly, whenever you want to draw more objects than you have at hand. When you need a new object, the pool searches for a free object to give you. If the "fish" is not found in the pool, the pool "gives birth" to a bunch of brand new objects and hands you one of them. If a free one was found, the pool just gives it to you. "But why", you'll wonder, "do I need an object pool that creates objects? Can't I just instantiate as many objects as I wish?". My answer is: Yes, you could. But not in all cases. There are some special cases, where to just pull the caught fish from the bucket is better (and definitely faster) then to catch a new fish.

Reusing objects (not classes)

If all objects were small and fast, the programmer would die from happiness, that's why the world serves us heavy tasks which need heavy objects. And heavy not only means that an object has many data embedded in properties and data structures, but also means heavy initialization code. Imagine you have a bunch of Dictionary objects, which are essentially the same, but are used to translate different languages. Well, they could easily be written as a single class, which takes an argument -- the desired language. The object then, connects to a database, pulls N megabytes, stores it in some internal data structures and is ready to be used. Now imagine that you should create a new object for every instance of its client. Well, I guess you don't want to waste a minute or so for each Dictionary creation, do you? So the problem is apparent, but the solution not yet. If there were some mechanism which allowed you to create the objects, store them anywhere, put them to sleep, and wake them only when you need them, you wouldn't have any problem, right? (Maybe.:) Right! Here's were object (not class) reuse come to help you. I've seen several implementations of object pooling, of which the best one is Microsoft's implementation of COM+ components pooling. I'll not compete with Microsoft (yet:), but will give the first (known to me) implementation of object pooling for .NET objects. So read along and enjoy...

History (or about COM+ pooling)

(This subsection's text is copy/pasted from Platform SDK/Component Services/ Services Provided by COM+/Object Pooling and is copyrighted (c) material of Microsoft corp.)
Object pooling is an automatic service provided by COM+ that enables you to configure a component to have instances of itself kept active in a pool, ready to be used by any client that requests the component. You can administratively configure and monitor the pool maintained for a given component, specifying characteristics such as pool size and creation request time-out values. When the application is running, COM+ manages the pool for you, handling the details of object activation and reuse according to the criteria you have specified.
You can achieve very significant performance and scaling benefits by reusing objects in this manner, particularly when they are written to take full advantage of reuse. With object pooling, you gain the following benefits:

  • You can speed object use time for each client, factoring out time-consuming initialization and resource acquisition from the actual work that the object performs for clients.
  • You can share the cost of acquiring expensive resources across all clients.
  • You can pre-allocate objects when the application starts, before any client requests come in.
  • You can govern resource use with administrative pool management—for example, by setting an appropriate maximum pool level, you can keep open only as many database connections as you have a license for.
  • You can administratively configure pooling to take best advantage of available hardware resources—you can easily adjust the pool configuration as available hardware resources change.
  • You can speed reactivation time for objects that use Just-in-Time (JIT) activation, while deliberately controlling how resources are dedicated to clients.

.NET pooling with ObjectPool

Well, as I mentioned in the Reusing objects section I'm too little to compete with Microsoft, but of the features listed above, the ObjectPool library can (cut-down version of the Marketing blah-blah):

  • speed object use time...
  • share the cost of acquiring expensive resources...
  • pre-allocate objects when the application starts...
  • construct (configure) objects, like COM+ (not mentioned above)

ObjectPool library in a nutshell

The library is as little as cool, or as cool as little, whichever sounds better, and in my favor:) It consists of one interface, one attribute class, and two classes, of which one is just a helper. But before I dive deeply into the library's implementation, I'll explain that there is no free lunch, and free poolable objects.

Poolable objects must meet some requirements to enable a single object instance to serve multiple clients. For example, they can't hold client state (internal object data, persisted only while serving a particular client) or have any thread affinity (as the object will be reused from different threads). In addition, all objects that wish to become poolable must implement the IPoolableObject interface, and be marked with the [PoolableAttribute] attribute. Inheriting the IPoolableObject interface enables the object to perform initialization when activated and clean up any client state on deactivation. Furthermore, object are notified when they are created for the first (and last) time, as well as when they are destroyed by the library and left alone with the Evil GC :). Often, it useful to write poolable objects in a somewhat generic fashion (like the Dictionary objects I mentioned above) so that they can be customized with some configuration state which will make them look distinct. For example, an object might be written to hold a generic SQL Server client connection, with a preliminary configured construction string. Well, unlike COM+, we have more freedom here, as this is a library and not a runtime framework, so our poolable objects could be initialized with any Object __gc*- derived (or boxed __value) class instance.

I am an IPoolableObject...

Just before I begin, all interfaces, classes, etc. are defined in the ObjectPooling namespace, so I'll omit it in the article.

It is very straightforward to implement the IPoolableObject (which so much resembles IObjectControl that I'm ashamed). Here's its definition:

MC++
// the interface which each poolable object should implement
// to qualify as poolable (similar to COM+ IObjectControl)
public __gc __interface IPoolableObject
{
public public:
    // called by the ObjectPool on each object, which needs
    // to be configured
    void    Construct (Object __gc* configuration);

    // called at first object creation
    void    Create ();

    // called at each activation (drawing from the pool)
    void    Activate ();

    // called at each deactivation (return in the pool)
    void    Deactivate ();

    // called at object destruction (leaving it to GC)
    void    Destroy ();
}; // IPoolableObject

As I said, it is very straightforward, but its tedious too. So I've written a very simple implementation, and named it BasePoolableObject. The class just implements overridable stubs, for its inheritors. The only useful thing it does alone is persisting the configuration object and exposing it as its property. Here's its implementation:

MC++
// helper class, implementing the IPoolableObject interface
// most poolable objects will derive from it and will override
// its methods
public __gc class BasePoolableObject : public IPoolableObject
{
protected protected:
    BasePoolableObject () :
	    m_configuration (0) // unconfigured by default
    {
    }
public public:
    // called by the ObjectPool class at runtime
    // not overridable
    void Construct (Object __gc* configuration)
    {
        this->m_configuration = configuration;
    }
    // overridable methods
    virtual void Create ()
    {
    }
    virtual void Activate ()
    {
    }
    virtual void Deactivate ()
    {
    }
    virtual void Destroy ()
    {
    }
    // properties
    __property Object __gc* get_Configuration ()
    {
        return (m_configuration);
    }
public protected:
    Object __gc* m_configuration;
}; // BasePoolableObject

An (almost) poolable object could now be created with no bigger effort than typing the following line of code:

MC++
public __gc class SampleObject : public BasePoolableObject {}

... so mark me with the PoolableAttribute

But I said almost. You have to do just one more thing. Apply to the poolable class the PoolableAttribute attribute, or in C# -- add [Poolable] on top of your class' declaration. However, you can control some interesting aspects of the pooling, so I'll give you some source code of the PoolableAttribute class. But even before I show you the code, you'll need to know, that the attribute class itself is marked with a .NET custom attribute -- the AttributeUsageAttribute one. It is needed to mark our Poolable as applicable only to classes, and mark it for single use.

MC++
[AttributeUsageAttribute (
    AttributeTargets::Class, // supported on classes only
    AllowMultiple = false)]  // and only once

Now, as I'm writing this article, I'm dreaming of the time I'll have some more free time to write for fun, I thought of having multiple ways to pool objects in the object pool, so don't worry when you see the PoolingType property of the attribute. It's here -- but its not take into consideration (yet).

MC++
[FlagsAttribute]
public __sealed __value enum PoolingType : int
{
    // normal pooling
    NormalPooling   = 0x0000001,
    // pool with weak references
    WeakReferences  = 0x0000002,
    // both
    WeakPooling     = NormalPooling | WeakReferences,
    // the pool decides at runtime how to pool the objects
    Runtime         = 0x0000004
};

// the cool attribute that provides information to the
// ObjectPool class
[AttributeUsageAttribute (
    AttributeTargets::Class, // supported on classes only
    AllowMultiple = false)]  // and only once
public __gc class PoolableAttribute : public Attribute
{
public public:

    // constructor (and overloads below)
    PoolableAttribute (
        PoolingType poolingType, // not used (yet)
        bool constructable, // supports construction/configuration
        int minPoolSize,         // minimum pool size
        int preferedPoolSize, // preferred pool size (not used yet?)
        int maxPoolSize,         // pool threshold
        int creationTimeout) :   // object creation timeout
            m_poolingType (poolingType),
            m_constructable (constructable),
            m_minPoolSize (minPoolSize),
            m_preferedPoolSize (preferedPoolSize),
            m_maxPoolSize (maxPoolSize),
            m_creationTimeout (creationTimeout)
    {
        CheckState ();
    }

    // overloaded constructors omitted for brevity...

    // properties
    __property PoolingType get_Pooling ()
    {
        return (m_poolingType);
    }

    __property void set_Pooling (PoolingType poolingType)
    {
        m_poolingType = poolingType;
        CheckState ();
    }

private private:
    void CheckState ()
    {
        if (m_minPoolSize > m_preferedPoolSize  ||
            m_minPoolSize > m_maxPoolSize       ||
            m_preferedPoolSize > m_maxPoolSize)
            throw (new ArgumentException (
                S"The condition min <= prefered <= " +
                S"max pool size was not met");
        if (m_creationTimeout < -1)
            m_creationTimeout = InfiniteCreationTimeout;
    }

    PoolingType m_poolingType;
    bool        m_constructable;
    int         m_minPoolSize;
    int         m_preferedPoolSize;
    int         m_maxPoolSize;
    int         m_creationTimeout;

    // default values -- change at will :)
    static const bool           DefaultConstructable = false;
    static const PoolingType    DefaultPoolingType = 
        PoolingType::NormalPooling;
    static const int            DefaultMinPoolSize = 1;
    static const int            DefaultPreferedPoolSize = 16;
    static const int            DefaultMaxPoolSize = 100000;
    static const long int       DefaultCreationTimeout = 60000;

public public:
    static const long int       MaxCreationTimeout = 
        0x80000000; // ~2 billions
    static const long int       InfiniteCreationTimeout = -1;

}; // PoolableAttribute

... and instantiate me via the ObjectPool

Now, when we're done talking, I'll show you a real poolable class in a couple of lines. It's not smart, it's not doing anything cool, it's just a poolable class I actually use in the sample (in the source code). Here it is:

MC++
[PoolableAttribute (
    PoolingType::NormalPooling, // not used (yet)
    true, // constructable
    5,    // 5 objects will be initially created in the pool
    16,   // prefered pool size is 16 objects (so what? :)
    18)]  // the pool will exhaust when 19th object is 
          // requested and none were returned in the pool
public __gc class SampleObject : public BasePoolableObject
{
public public:
    // override
    void Create ()
    {
        // do some very serious (heavy) stuff right here
    }

    // just a simple method showing that the object is working 
    void DoWork ()
    {
        Console::WriteLine (S"...");
    }
}; // SampleObject

Yours could be a two-liner, e.g.

MC++
[PoolableAttribute(PoolingType::NormalPooling, true, 5, 16, 18)]
public __gc class SampleObject : public BasePoolableObject {}

Now you've seen how to create a poolable class, but you haven't seen how to use it, right? Now is the time! All object creation requests are handled through the ObjectPool class. It maintains instances of the poolable class in a pool, ready to be activated for any client requesting the object, just like the COM+ object manager. And because, I'm better in writing code, than "talking" look how easy it is to create an object pool:

MC++
// ain't it cool? :)
ObjectPool __gc* pool = new ObjectPool(__typeof(SampleObject));

But you haven't created the pool already:) I intentionally provided a method for creating and destroying an object pool to avoid the overhead if you're planning to use it but it is possible that you won't. So the pool is not actually created in the constructor, but could be very easily created like in the code below:

MC++
// 5 objects will be created and will wait
// to be drawn from the pool
pool->CreatePool (S"Hello World!"); // configure newly created 
// objects with this string
// BTW, I made this method to return a pointer to the object 
// pool, so the lazy guys (like me) could write:
ObjectPool __gc* pool = (new ObjectPool ())->CreatePool(S"...");

By the way, the pool can work in two modes: "exhaustible" and "non-exhaustible". In the former mode, when there are no more free objects in the pool, but a request to draw one is executed, the pool throws an exception. In the non-exhaustible mode, the pool blocks, waiting for a free object to be returned, and returns it to the client. If one properly uses the pool (i.e. draws objects only when she needs them, and returns them when she's finished using them), she wouldn't need the pool to work in non-exhaustible mode (?). However, under a very heavy load, the pool may really get exhausted, and throwing exceptions is not a very cool solution, so I made the pool working in one of the aforementioned modes. Here are some examples of creating both kinds of pools:

MC++
// method definition (3 overloads skipped, see their usage below)
ObjectPool __gc* CreatePool (Object __gc* configuration,
    bool canExhaust);

// exhaustable, with no configuration
pool->CreatePool (); 

// non-exhaustable, no configuration
pool->CreatePool (false); 

// exhaustable, a stack for configuration
pool->CreatePool (new Stack ()); 

// non-exhaustable, string configuration
pool->CreatePool (false, S"Hello"); 

Now, when we have the object pool up and ready, we're ready in our turn to go "fish". The only thing, that is easier than the pooled object instantiation is the normal instantiation via the new operator:) But here's how you'd instantiate an object via the pool:

MC++
// poolable object instantiation version 1
SampleObject __gc* obj = pool->Draw ();

I'm lying. It's not that easy, as the objects are created at runtime via a mechanism, known as reflection. Look at the method below to see why:

MC++
// helper function, which creates poolable objects
IPoolableObject __gc* CreatePoolableObject ()
{
    IPoolableObject __gc* po = 
        static_cast<IPoolableObject __gc*> (
            Activator::CreateInstance (m_type));
    // construct the object if needed
    if (m_constructable)
        po->Construct (m_configuration);
    // call object's create method
    po->Create ();
    return (po);
}

Because the CreateInstance method of the Activator class returns Object, the only thing I could do better is to cast it to IPoolableObject, as I know that only these kinds of objects are present in the pool. That's all I can do. Well, to convert it to SampleObject, we need at least (and enough) a static_cast, like in the example below:

MC++
// poolable object instantiation version 2 (correct)
SampleObject __gc* obj = static_cast<SampleObject __gc*> (pool->Draw ());

Now, it's time to see the ObjectPool class, isn't it? OK, but before your rush, I'm warning you that I deleted the "uninteresting" code from the listing below, so take a look at the "ObjectPool.h" file for details:

MC++
// constructor
ObjectPool (Type __gc* type)
    : m_type (type),      // type from which to create objects
      m_canExhaust (true),
      m_created (false),  // no created yet
      m_pool (0),         // ...
      m_freeObjects (0),  // ...
      m_canPoolType (false) // unknown at this time
{
    // check if the type implements the IPoolableObject interface
    if (m_type->IsSubclassOf (__typeof (IPoolableObject)))
        throw (new ArgumentException (
            S"The type should derive from IPoolableObject", 
            S"type"));
    m_sync = new SyncGuard (this);
    // obtain the PoolableAttribute and its properties
    ObtainTypeInformation ();
}

// ...destructor skipped...

// creates the actual pool
ObjectPool __gc* CreatePool (
    Object __gc* configuration,
    bool canExhaust) // see m_canExhaust for explanation
{
    Trace (S"CreatePool: Creating pool");
    if (m_created)
        throw (new InvalidOperationException(
            S"Pool has been already created"));

    m_sync->Lock ();
    m_canExhaust = canExhaust;
    m_configuration = configuration; // save configuration
    // create helper objects with capacity = the prefered size
    m_pool = new Hashtable (m_preferedSize);
    m_freeObjects = new Stack (m_preferedSize);
    m_created = true;
    m_sync->Unlock ();
    // create only "minimum pool size" objects
    Grow (m_minSize); // create initial pool with minimum size
    return (this);
}

// ...overloads skipped...

// destroys the pool
void DestroyPool ()
{
    Trace (S"DestroyPool: Destroying pool");
    if (! m_created)
        throw (new InvalidOperationException (
            S"Pool cannot be destroyed, becuase " + 
            S"it has not been created"));

    m_sync->Lock ();
    IDictionaryEnumerator __gc* dict = m_pool->GetEnumerator ();

    // walk all objects, deactivate and destroy them
    while (dict->MoveNext ())
    {
        IPoolableObject __gc* po = 
            static_cast<IPoolableObject __gc*> (dict->Value);

        po->Deactivate ();
        po->Destroy ();
    }
    // clear internal helper objects
    m_pool->Clear ();
    m_freeObjects->Clear ();
    m_created = false;
    m_sync->Unlock ();
}

// Draws an object from the pool. If there aren't any objects 
// created, a new one is created, otherwise an existing one 
// is reused.
IPoolableObject __gc* Draw ()
{
    Trace (S"Draw: Drawing object");
    if (! m_created)
        throw (new InvalidOperationException (
            S"Pool has not been created"));

    // check if type can be pooled
    if (! m_canPoolType)
    {
        // can't activate the object, but at least can configure it
        Trace (S"Draw: Type not poolable, creating it");
        return (CreatePoolableObject ());
    }
    // this one is a poolable type
    // search for available item in the pool and return it
    IPoolableObject __gc* po = InternalDraw ();
    if (po != 0)
    {
        Trace (S"Draw: Object activated and used from the pool");
        return (po);
    }
    else // no free ones
    {
        Trace (S"Draw: No free objects, trying to grow");
        if (Grow (-1))
        {
            // after the grow there will be free ones now
            return (InternalDraw ());
        }
        else
        {
            if (m_canExhaust)
            {
                throw (new InvalidOperationException (
                    S"Pool exhausted!"));
            }
            else // the pool should wait until a free object returns
            {
                Trace (S"Draw: Waiting for free object " + 
                    S"to return in the pool");

                while (po == 0)
                {
                    po = InternalDraw ();
                    Thread::Sleep (0); // give just a bit of time
                }
                Trace (S"Wait finished. At least " + 
                    S"one free object returned.");

                return (po);
            } // if (m_canExhaust)
        } // if (Grow (-1))
    } // if (po != 0)
} // Draw

// Returns an object to the pool to be reused
void Return (IPoolableObject __gc* object)
{
    Trace (S"Return: Returning object in the pool");
    if (! m_created)
        throw (new InvalidOperationException (
            S"Pool has not been created"));

    if (! m_pool->ContainsKey (object))
        throw (new InvalidOperationException (
            S"Object was not created by this object pool"));

    if (! m_canPoolType)
    {
        Trace (S"Return: Object not poolable, " + 
            S"leaving GC do its work");
        return; // can't do anything with 
                //unpoolable objects, but GC can
    }

    // cleanup and return the item in the pool
    object->Deactivate ();
    m_sync->Lock ();
    m_freeObjects->Push (object);

    Trace (S"Return: Object deactivated and " + 
        S"returned in the pool");

    m_sync->Unlock ();
} // Return

// ... trace methods skipped ...

void ObtainTypeInformation ()
{
    Trace (S"ObtainTypeInformation: Obtaining type information");
    m_sync->Lock ();
    // get the type's custom attributes
    Object __gc* attArray __gc[] = 
        m_type->GetCustomAttributes (true);

    Attribute __gc* atts __gc[] = 
        static_cast<Attribute __gc* __gc[]> (attArray);

    // search for our PoolableAttribute
    for (int i=0; i<atts->Length; i++)
    {
        if(atts [i]->GetType () == __typeof(PoolableAttribute))
        {
            // found?
            m_canPoolType = true; // type can be pooled
            // retrieve pooling information
            PoolableAttribute __gc* pt = 
                static_cast<PoolableAttribute __gc*> (atts [i]);

            m_constructable = pt->Constructable;
            m_poolingType = pt->Pooling;
            m_minSize = pt->MinPoolSize;
            m_preferedSize = pt->PreferedPoolSize;
            m_maxSize = pt->MaxPoolSize;
            m_creationTimeout = pt->CreationTimeout;
            break; // no need to search further
        }
    }
    m_sync->Unlock ();
} // ObtainTypeInformation

// Searches for a free item in the pool. If one is found, it is
// returned, otherwise the method returns 0 (null) to notify the
// main Draw method (which would eventually grow the pool)
IPoolableObject __gc* InternalDraw ()
{
    m_sync->Lock ();
    // check for free objects
    if (m_freeObjects->Count > 0) // found?
    {
        // get one
        IPoolableObject __gc* po =
            static_cast<IPoolableObject __gc*>(
                m_freeObjects->Pop ());

        m_sync->Unlock ();
        // ... and activate it
        po->Activate ();
        Trace(S"InternalDraw: Free object found and activated");
        return (po);
    }
    m_sync->Unlock ();
    Trace (S"InternalDraw: Could not find a free object");
    return (0); // no free object found
} // InternalDraw

// grows the pool as needed : creates objectCount 
// number of objects
bool Grow (int objectCount)
{
    Trace (S"Grow: Attempting to grow");
    m_sync->Lock ();
    // -1 means, that the pool should decide how many objects it
    // can create
    if (objectCount == -1) // calculate the number of objects
        objectCount = Math::Min (m_maxSize - m_pool->Count, 
            DefaultGrowSize);

    if (objectCount <= 0) // pool has exhausted
    {
        Trace (S"Grow: Pool exhausted. Can't grow pool");
        m_sync->Unlock ();
        return (false);
    }
    Trace (S"Grow: Creating and constructing {0} objects", 
        __box (objectCount));

    for (int i=0; i<objectCount; i++)
    {
        // create brand new objects
        IPoolableObject __gc* po = CreatePoolableObject ();
        // activated at InternalDraw
        m_pool->Add (po, po);       // add to pool
        m_freeObjects->Push (po);   // and to the free obj list
    }
    m_sync->Unlock ();
    Trace (S"Grow: Grown successfully");
    return (true);
} // Grow

// ... CreatePoolableObject() skipped -- show somewhere above ...

// pool's internal properties
Type __gc*      m_type;           // type of objects to create
// If a pool can exhaust, then when its limit is reached 
// (i.e. there are no free objects, and an attempt to get 
// one is made), the pool throws a PoolExhausted exception. 
// Otherwise, if a pool is not exhaustable, then when its 
// limit is reached, the pool blocks until a free object is 
// avaiable, and returns it to the caller

bool            m_canExhaust;

// synchronization object
SyncGuard __gc* m_sync;           

// indication of the pool status
bool            m_created;        

// helper to store the objects in
Hashtable __gc* m_pool;           

// a free list of objects
Stack __gc*     m_freeObjects;    

// indication whether the type is poolable
bool            m_canPoolType;    

// saved configuration from the Create method
Object __gc*    m_configuration;  


// pool properties, based on the Pooling type
// these are UNITIALIZED in the constructor, because we 
// don't know how to initialize them
PoolingType     m_poolingType;      // see the PoolableAttribute
bool            m_constructable;
int             m_minSize;
int             m_maxSize;
int             m_preferedSize;
int             m_creationTimeout;

static int      DefaultGrowSize = 16;   // default pool grow size

I don't think that the synchronization will be interesting to anyone, so I'll say only that it uses the Monitor class from the System::Threading namespace. You can peek at the code and see how simple it is. Initially I thought I should use the ReaderWriterLock class, but after a little thought (and a bang on the head by a friend:) I dropped it and stuck to the sufficient Monitor class.

Using the library (properly?)

Well, I guess you've used it already, reading the topics above. If you haven't read them and jumped right here, I'd suggest that you go back and read them. However, I'd like to say a couple of words about the (proper) use of the library. So read ahead...

[Poolable] needs to be smart?

.NET attribute classes are not dynamic, so they can't be smart. I mean that before you place that [Poolable] attribute you should think how many instances are likely to be used initially, on the average and their maximum. Once you apply the attribute to the class, you'll have to recompile it when you change it, ok? So you'll have to carefully choose the values for minPoolSize, preferedPoolSize and maxPoolSize, as well the timeout for the (initial) object creation. If you don't want to set a creation timeout, simply pass InfiniteCreationTimeout. By default, all objects are attempted to be created in 60 seconds. If the creation fails because the object did not respond in a timely manner, the object is discarded as if it were never created. But relax :), I'll implement the object creation timeout next week, because I'm very busy right now. So you can expect that your objects will be created no matter how much time they waste...

Creating the pool

Object pools are created per type, e.g. class. Once you create an object pool of type SomeType, you cannot draw object of AnotherType, or return AnotherType object in the SomeType pool. Sounds restrictive, but that's the proper way. The other way is a way to the programming hell, believe me. This is not your old (?) COM+, and I'm not Microsoft (though I'd like to work there one day :) As you can see from the ObjectPool source above, when the ObjectPool class is instantiated, no pool is really created, until you call the Create method.

MC++
// does not actually create a pool of objects, just prepares 
// the internal state of the object pool
ObjectPool __gc* pool = 
    new ObjectPool (__typeof (SampleObject));

// the line below creates the pool
Hashtable __gc* objectState = new Hashtable ();
objectState->Add (S"key1", s"Value1");
// ... some more...
// configure objects with this hash table
pool->CreatePool (objectState); 

Drawing objects from the pool

Before you write the code to draw an item from the pool, write the code for returning it to the pool. If you forget to return the object to the pool, nothing bad will happen to Windows, the CLR and probably your application. What WILL happen is that you'll loose the object. The object pool keeps track of the objects, so at its destructor it properly deactivates and destroys ALL objects, no matter if they were returned to the pool or not. But the object will not be in the "free list", so it will be unusable. Again, be sure to specify a good initial number of objects that need to be created to avoid the penalty of creating objects at critical times during the runtime of your application. (Also, you can change the DefaultGrowSize constant in the source code of the ObjectPool class or even expose it as the PoolableAttribute's property. If you forgot how an object is drawn from the pool, here's the code:

MC++
SampleObject __gc* obj = 
    static_cast<SampleObject __gc*> (pool->Draw ());

Returning objects to the pool

Please, return them :) Don't let them get lost, as later, the Evil GC will find them, and then who knows? :) I'll provide the "return object" construct only because I know most of you have skipped (some of) the previous sections.

MC++
pool->Return (obj); // nothing more simple

Destroying the pool

There's no problem if you forget to destroy the pool. When the GC gets it later, its destructor will deactivate and destroy all pooled objects. However, if your poolable class uses unmanaged resources, and releases them in its Destroy or Deactivate methods, it would be nice to destroy the pool. The code to do that is just not worth to type.

Compiling ObjectPool

There is Src folder, containing 4 other folders: Bin, ObjectPooling, ObjectPoolingTest and CSharpPoolingTest. When you build the solution file, all executables and assemblies will go into the Bin folder. The ObjectPooling folder contains the library's source files, the ObjectPoolingTest folder contains an example of ObjectPool library client, and the CSharpPoolingTest -- guess what:) Now, I won't excuse for not building a command-line batch file for building the library and the sample application. It's fairly easy to write your own, as the library only uses mscorlib.dll and System.dll. If you have VS.NET or VC++ .NET, just build the solution. I'm a very busy boy, so I never have the time to write such batch files, sorry!

TODO(s)

    There are two things I haven't done (or done partially):
  • Object creation timeout is not inspected and creation is not monitored or done asynchronously, though a simple thread, pulsing an event would do a very good job, but... next week I guess.
  • The pooling with the WeakReference is not implemented, as I'm just figuring out how to do it:) The idea, by the way, is Jeffery Richter's so I'm giving him his credit right here and right now -- Thanks Jeff!

Conclusion

Thanks for reading the article! I was expecting that some people would peek at it, and I'm glad the YOU have actually read the whole of it, thanks again!

Object pooling is a very old programming paradigm, but I remember since MTS that very few programmers take advantage of it. Furthermore, pooling COM+ components was not very recommended for VB COM components, so only the C++ guys could make their objects poolable, fast, free-threaded, etc. (And I was a VB programmer once :) Now, you can pool your .NET objects not only with the runtime provided for COM+ components written in .NET languages, but for every simple object that deserves such a special attention. Of course, I'm sure there are many guys out there that could laugh at the implementation, suggest super-duper data structures beside the hash table and the stack I've used. I'll tell one thing -- CodeProject is to teach and learn, not to show up how COOL you (may think you) are, and the .NET Hashtable and Stack classes are present in every .NET-enabled :) machine.

Reporting bugs

I don't know any (as I just wrote the article:), so if you find one, please, let me know. My e-mail address is at the bottom of the article (and an alternative one is stoyan_damov[at]hotmail.com, but PLEASE!!! do not spam me. Thanks!

Disclaimer

The software comes “AS IS”, with all faults and with no warranties.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Bulgaria Bulgaria
I'm crazy about programming, bleeding-edge technologies and my wife, Irina. Thinking seriously to start living in Centurian time.

The image shows me, happy :P

My blog

Comments and Discussions

 
Generalnew destroy paradigm Pin
idishkov19-Apr-04 1:23
idishkov19-Apr-04 1:23 
GeneralSource zip file corrupt Pin
Anonymous27-Jan-04 15:19
Anonymous27-Jan-04 15:19 
GeneralEvent based return to pool... Pin
rdcotter13-Sep-03 3:56
rdcotter13-Sep-03 3:56 
GeneralRe: Event based return to pool... Pin
Stoyan Damov13-Sep-03 12:35
Stoyan Damov13-Sep-03 12:35 
First, thanks!

I'm glad that you've found the article useful (even though it is quite outdated now). Do you mean to extend the IPoolableObject interface with a ReturnToPool method? I'm asking you, because I couldn't "connect" your event idea in my brain at "think time"Smile | :) If that's your idea, it looks a bit dangerous to me. Imagine that you get an object from the pool, pass it to someone else's code and s/he returns *your* object to the pool, even if you're not expecting thatFrown | :(

Anyway, this is not a reference implementation so go ahead and change it at will;)

R. Cotter wrote:
Thoughts?

Yup, many... but I'm writing something cooler right now, so... you're on your own;P

Cheers,
Stoyan

He who fails to design for performance, performs a design failure. (Dov Bulka, David Mayhew in Efficient C++ Programming Techniques)
GeneralGood Job Pin
Aydin Bakir24-Nov-02 21:33
Aydin Bakir24-Nov-02 21:33 
GeneralRe: Good Job Pin
Stoyan Damov6-Dec-02 23:10
Stoyan Damov6-Dec-02 23:10 
GeneralThanks!!! Pin
Senkwe Chanda29-Oct-02 0:54
Senkwe Chanda29-Oct-02 0:54 
GeneralRe: Thanks!!! Pin
Stoyan Damov29-Oct-02 2:28
Stoyan Damov29-Oct-02 2:28 
GeneralNice work Pin
Sijin28-Oct-02 19:03
Sijin28-Oct-02 19:03 
GeneralRe: Nice work Pin
Stoyan Damov28-Oct-02 22:43
Stoyan Damov28-Oct-02 22:43 
GeneralVery impressive! Pin
Jon Rista28-Oct-02 11:52
Jon Rista28-Oct-02 11:52 
GeneralRe: Very impressive! Pin
Stoyan Damov28-Oct-02 22:52
Stoyan Damov28-Oct-02 22:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.