Click here to Skip to main content
Click here to Skip to main content

Object Pool Class for C#/.NET Applications

By , 28 Apr 2011
 

Introduction

This article is about an easy to use thread safe object pool class. All you need is to implement an interface IPoolable and your class is good for object pooling :). A code snippet in the Using the Code section below shows how easy it is to pool your objects.

Background

While working on a gaming platform, I needed to show multiple gaming forms as quickly as possible. Forms creation time was becoming a bottle neck as it contained lots of controls and docking windows. To save creation time and to reuse gaming forms (instead of closing them and letting the garbage collector delete them from memory), I used object pooling.

Whenever an end user opens a gaming form, I take it from an object pool (instead of creating a new one every time). Similarly, when the end user closes the gaming forms, I simply make them invisible and take them back to the object pool.

// Obtain objects from pool
SampleForm x = ObjectPool.New<sampleform>();

// return objects to object pool
ObjectPool.Delete<sampleform>(x);

// again obtain objects from object pool, note that
// objects will be reused
SampleForm x2 = ObjectPool.New<sampleform>();

Another reason for having an object pool is to bring scalability in our gaming server by reusing server objects through none other than object pooling. We get hundreds of thousands of requests from gaming clients every minute. If we serve objects like packets and socket info is created with every request, then the garbage collector could melt down the server and our server would not scale.

Using the code

Using the ObjectPool class is pretty straightforward. All you need is to implement an interface IPoolable in your class:

public class SampleClass : IPoolable
{
    public void Create()
    {
    }

    public void New()
    {
    }

    public void Delete()
    {
    }
}
  • Create method is straightforward and it is there to do time the costly operation of object creation. Note that your class must have a default constructor to participate in object pooling. In the Create method, write the time costly logic of creating child controls, obtaining various things from database, etc. This method is called once right after the default constructor from the ObjectPool class.
  • New method is used to initialize your object. It is called whenever an object is served from the ObjectPool class. Use this method to reset your object to its initial state. E.g., adding events, initializing variables. Keep the code to a minimum in New. It should return as soon as possible to keep it efficient. At times, you will keep this method empty and put all your un-initialization logic in Delete, which is called when you return the object to the object pool.
  • Delete method is used to un-initialize your object. E.g., removing events, resetting class members to initial state, etc. This method is called whenever you return your object to the object pool.
public void Test()
{
    // Obtain objects from pool
    SampleForm x = ObjectPool.New<sampleform>();
    SampleForm x1 = ObjectPool.New<sampleform>();
    SampleForm x2 = ObjectPool.New<sampleform>();
    SampleClass x3 = ObjectPool.New<sampleclass>();

    // return objects to object pool
    ObjectPool.Delete<sampleform>(x);
    ObjectPool.Delete<sampleform>(x1);
    ObjectPool.Delete<sampleform>(x2);
    ObjectPool.Delete<sampleclass>(x3);

    // again obtain objects from object pool, note that
    // objects will be reused
    SampleForm x4 = ObjectPool.New<sampleform>();
    SampleClass x5 = ObjectPool.New<sampleclass>();
}

Points of interest

I wanted to keep the API public interface simple, clean, and type safe, and it took some time to reach the current form. Deciding between interface and Generics with the right constraints was kind of tricky, mostly due to the where syntax :). Besides that, making the class thread safe and using Dictionary/Stack as data structures seemed a bit of trade off between performance and ease of use. Here is the full source code. Please do let me know of any bugs or any other improvements that you think are possible.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace App.Model
{
    #region Example Usage
    namespace ObjectPoolTester
    {
        #region ObjectPoolTester
        public class ObjectPoolTester
        {
            public void Test()
            {
                // Obtain objects from pool
                SampleForm x = ObjectPool.New<sampleform>();
                SampleForm x1 = ObjectPool.New<sampleform>();
                SampleForm x2 = ObjectPool.New<sampleform>();
                SampleClass x3 = ObjectPool.New<sampleclass>();

                // return objects to object pool
                ObjectPool.Delete<sampleform>(x);
                ObjectPool.Delete<sampleform>(x1);
                ObjectPool.Delete<sampleform>(x2);
                ObjectPool.Delete<sampleclass>(x3);

                // again obtain objects from object pool, note that
                // objects will be reused
                SampleForm x4 = ObjectPool.New<sampleform>();
                SampleClass x5 = ObjectPool.New<sampleclass>();
            }
        }
        #endregion

        #region SampleClass
        public class SampleClass : IPoolable
        {
            public void Create()
            {
            }

            public void New()
            {

            }

            public void Delete()
            {

            }
        }
        #endregion

        #region SampleForm
        public class SampleForm : IPoolable
        {
            public void Create()
            {
            }

            public void New()
            {

            }

            public void Delete()
            {

            }
        }
        #endregion
    }
    #endregion

    #region IPoolable
    public interface IPoolable
    {
        void Create();
        void New();
        void Delete();
    }
    #endregion

    #region ObjectPool
    public class ObjectPool
    {
        #region Data Members
        private static Dictionary<system.type,> pools = new Dictionary<type,>();

        #endregion

        #region New

        public static T New<t>() where T : IPoolable, new()
        {
            T x = default(T);

            if (pools.ContainsKey(typeof(T)))
            {
                x = (T)pools[typeof(T)].Pop();
            }
            else
            {
                lock (pools)
                {
                    pools[typeof(T)] = new PoolableObject(10);
                }
            }

            if (x == null)
            {
                x = new T();
                x.Create();
            }
            x.New();

            return x;
        }

        #endregion

        #region Delete
        public static void Delete<t>(T obj) where T : IPoolable
        {
            if (pools.ContainsKey(typeof(T)))
            {
                obj.Delete();
                pools[typeof(T)].Push(obj);
            }
            else
            {
                throw new Exception("ObjectPool.Delete can not be 
                called for object which is not created using ObjectPool.New");
            }
        }

        #endregion

        #region Clear

        public static void Clear()
        {
            lock (pools)
            {
                foreach (PoolableObject po in pools.Values)
                {
                    po.Clear();
                }

                pools.Clear();
            }
        }
        #endregion
    }
    #endregion

    #region PoolableObject
    public class PoolableObject
    {
        #region Data Members
        private Stack<ipoolable> pool;

        #endregion

        #region Ctor
        public PoolableObject(int capacity)
        {
            pool = new Stack<ipoolable>(capacity);
        }
        #endregion

        #region Properties
        public Int32 Count
        {
            get { return pool.Count; }
        }
        #endregion

        #region Pop
        public IPoolable Pop()
        {
            lock (pool)
            {
                if (pool.Count > 0)
                {
                    return pool.Pop();
                }

                return null;
            }
        }
        #endregion

        #region Push
        public void Push(IPoolable obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("Items added to a Pool cannot be null");
            }

            lock (pool)
            {
                pool.Push(obj);
            }
        }
        #endregion

        #region Clear
        public void Clear()
        {
            lock (pool)
            {
                pool.Clear();
            }
        }
        #endregion
    }
    #endregion
}

History

First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Syed Rafey Husain
Software Developer (Senior)
Pakistan Pakistan
Member
Microsoft platform developer. Founder: rafeysoft.com

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionPooled Object Garbage Collectormemberjaimebula23 Jun '11 - 15:53 
Have you written the garbage collector for pooled objects?
AnswerRe: Pooled Object Garbage CollectormemberSyed Rafey Husain24 Jun '11 - 1:51 
No. But for a really simply GC, all you need is a Timer and cleaning objects on its tick while collection is locked.
Regards and nice day,
Rafey, Husain.

GeneralRe: Pooled Object Garbage Collectormemberjaimebula24 Jun '11 - 3:57 
Just what I was thinkg of! Any recomendation of where the timestamp should be placed?
GeneralRe: Pooled Object Garbage CollectormemberSyed Rafey Husain24 Jun '11 - 21:58 
ObjectPool class.
Regards and nice day,
Rafey, Husain.

GeneralGood Stuff!!!memberdepakb28 Apr '11 - 17:45 
Don't you think making the use ConcurrentStack (http://msdn.microsoft.com/en-us/library/dd267331.aspx) will even further simplify your code (you need .NET 4.0 for this)? Anyway, thanks for very good stuff.
GeneralRe: Good Stuff!!!memberSyed Rafey Husain28 Apr '11 - 19:11 
This is so cool Smile | :) We are doing our gaming platform for around 2 years in .NET 3.5. So unfortunately, I can not use ConcurrentStack. But thanks for pointing it out.
Regards and nice day,
Rafey, Husain.

GeneralObjectPool and "Thread safe", commentsmemberkornman0028 Apr '11 - 7:08 
If the ObjectPool is suppose to be thread safe, shouldn't the Stack method calls in New and Delete be inside of a lock as well?
 
Also, you may wish to compare the efficiency of your current implementation with another using Dictionary's TryGetValue (which would return you the type's Stack object, if one exists)
 
Some better terminology may be "Acquire" (for New) and "Release" (for Delete) due to what is actually happening (Create sounds fine, but you could also use Initialize...but I'm not here to argue semantics). Another thing you may consider is a LFU (least-frequently-used) algorithm setup, in the event that you dig the type's pool too deep and it now has more objects than it ever frequently needs (so what started off as a CPU battle, now starts to trickle over into a memory battle).
 
edit: Whoops, apologies, I just realized the Dictionary was of Type and PoolableObject (which locks the underlying Stack). I blame my lack of sleep and the faulty codeblock formatting for that error Sleepy | :zzz:
GeneralRe: ObjectPool and "Thread safe", commentsmemberSyed Rafey Husain28 Apr '11 - 19:08 
Thanks for feedback. I really appreciate that. I will compare TryGetValue with Contains/GetValue efficiency. I hope TryGetValue would be more efficent. I am not sure it would be efficient if I simply put GetValue dictionary inside a try/catch and let it fail safely.
 
I was also looking for a better semantics Smile | :) and thanks with coming up with Acquire/Release. With New/Delete I wanted to give a sense of construction/destruction of object while keeping costly (and static) construction inside Create.
 
You are right on memory battle. We are planning to have a separate job that will run at regular intervals and weed out unused/least frequently used objects. I think object pool garbage collector should be a separate entity from pool itself?
Regards and nice day,
Rafey, Husain.

GeneralRe: ObjectPool and "Thread safe", commentsmemberkornman0028 Apr '11 - 20:22 
Glad to have helped Smile | :)
 
The few times I've seen an LRU/LFU cache implemented, the removal policy (ie, the garbage collector logic in your case) was typically implemented via an interface or a functor (in C++, delegate in C#), that was given to the cache's constructor. From there, the cache's "think()" method was called for every game tick/update, which would then use the removal policy object to update the underlying entry statistics (if it records anything at all, eg, amount of ticks since last Release) and decide what action to take.
 
So you could probably keep with the simple pool, but have an inheriting class which provides the functionality of an LRU/LFU cache and allows for a removal policy object. However, with your current implementation you could run into a problem since you use a single "ObjectPool" for multiple types. You could very well want to have a stricter or lax policy for different types (eg, depending on their instance's memory usage)
GeneralRe: ObjectPool and "Thread safe", commentsmemberSyed Rafey Husain28 Apr '11 - 22:04 
Just to share some thoughts.
 
To free our design from maintaining statistics, we could have LastAcquireDateTime for every PoolableObject (i.e. when object was last acquired/new'ed from pool). A method within ObjectPool class could inspect LastAcquireDateTime with regular interval and remove object if it falls *below* certain threshold Acquire frequency.
 
Another way could be, IPoolable may have an advisory functions one of which returns ExpectedAcquireFrequency (a TimeSpan). During inspection ObjectPool will use this ExpectedAcquireFrequency advisory value to decide if it is the right time to remove an object.
Regards and nice day,
Rafey, Husain.

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 28 Apr 2011
Article Copyright 2011 by Syed Rafey Husain
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid