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.
SampleForm x = ObjectPool.New<sampleform>();
ObjectPool.Delete<sampleform>(x);
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()
{
SampleForm x = ObjectPool.New<sampleform>();
SampleForm x1 = ObjectPool.New<sampleform>();
SampleForm x2 = ObjectPool.New<sampleform>();
SampleClass x3 = ObjectPool.New<sampleclass>();
ObjectPool.Delete<sampleform>(x);
ObjectPool.Delete<sampleform>(x1);
ObjectPool.Delete<sampleform>(x2);
ObjectPool.Delete<sampleclass>(x3);
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()
{
SampleForm x = ObjectPool.New<sampleform>();
SampleForm x1 = ObjectPool.New<sampleform>();
SampleForm x2 = ObjectPool.New<sampleform>();
SampleClass x3 = ObjectPool.New<sampleclass>();
ObjectPool.Delete<sampleform>(x);
ObjectPool.Delete<sampleform>(x1);
ObjectPool.Delete<sampleform>(x2);
ObjectPool.Delete<sampleclass>(x3);
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.