Introduction
This article presents an implementation of a generic, concurrent object pool. This one is not "yet another object pool" and was designed and implemented with the following properties in mind:
- Generic
- Thread-Safe
- No Memory Leaks
Additionally, this implementation presents a production-level code. It has been tested extensively and works well.
Background
"The object pool pattern is a software creational design pattern that uses a set of initialized objects kept ready to use, rather than allocating and destroying them on demand. A client of the pool will request an object from the pool and perform operations on the returned object. When the client has finished, it returns the object, which is a specific type of factory object, to the pool rather than destroying it. " - Wikipedia.
In .NET, such collection has several advantages:
- As the objects are never disposed, it minimizes garbage that a GC has to collect, therefore making a GC pause shorter.
- It allows to increase the performance if you are allocating/destroying a massive amount of objects.
- As the garbage is minimized, so is memory fragmentation.
Using the code
Using the ConcurrentPool<T>
is very easy. First of all, derive your object from RecyclableObject
and implement the Recycle()
method. Alternatively, you could implement the IRecyclable
interface yourself.
public class MyObject : RecyclableObject
{
public string Name { get; set; }
public override void Recycle()
{
this.Name = String.Empty;
}
}
After that, you might want to instantiate an instance of ConcurrentPool<T>
. This can be done in several ways, since the pool needs to be able to allocate the object itself. So, if you provide a default constructor on your recyclable object, you can instantiate a pool simply as:
var pool = new ConcurrentPool<MyObject>("Pool of MyObject");
If not, you can also provide a CreateInstanceDelegate
to act as a constructor:
var pool = new ConcurrentPool<MyObject>("Pool of MyObject", () => new MyObject() );
Finally, acquiring and releasing the objects is done via a disposable pattern.
using (MyObject instance = pool.Acquire())
{
}
Alternatively, you could call TryRelease()
to attempt to release the object back to the pool.
MyObject instance = pool.Acquire();
instance.TryRelease();
And that's it!
Points of Interest
Now, let's have a look at how our ConcurrentPool<T>
works.
In the beginning of the article, I mentioned that the presented implementation is generic, thread-safe, and ensures that no memory is leaked. While the first point is quite straightforward, the second is done via storing internally the items in a ConcurrentQueue<T>
, from .NET Framework 4+.
We mentioned that RecyclableObject
implements the IDisposable
pattern, consider the following test:
static void Test1()
{
Console.WriteLine();
Console.WriteLine(" Test 1");
Console.WriteLine(" ******");
Console.WriteLine();
using (A a1 = poolOfA.Acquire())
{
Diagnose("a1 acquired.", poolOfA);
}
Diagnose("a1 disposed.", poolOfA);
}
static void Test2()
{
Console.WriteLine();
Console.WriteLine(" Test 2");
Console.WriteLine(" ******");
Console.WriteLine();
AllocateMany();
Diagnose("function ended.", poolOfA);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Thread.Sleep(100);
Diagnose("GC collected.", poolOfA);
}
static void AllocateMany()
{
for (int i = 0; i < 10; ++i)
{
A instance = poolOfA.Acquire();
}
Diagnose("10 instances acquired.", poolOfA);
}
In this snippet of code, Test2()
attempts to leak memory by not disposing the objects manually. In that case (as we implement the IDisposable
pattern), the objects are finalized by GC, and instead of being destroyed they are resurrected and are pushed back to the ConcurrentPool
.
> Pool created.
Pool contains 0 items, 0 in use, 0 available.
Test 1
******
> a1 acquired.
Pool contains 1 items, 1 in use, 0 available.
> a1 disposed.
Pool contains 1 items, 0 in use, 1 available.
Test 2
******
> 10 instances acquired.
Pool contains 10 items, 10 in use, 0 available.
> function ended.
Pool contains 10 items, 10 in use, 0 available.
> GC collected.
Pool contains 10 items, 0 in use, 10 available.
History
- 21 October 2012 - Initial release.