Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Concurrent Object Pool, the Right Way

0.00/5 (No votes)
22 Apr 2013 1  
An implementation of a generic, concurrent object pool with smart memory management.

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:

  1. Generic
  2. Thread-Safe
  3. 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
{
    /// <summary>
    /// Some dummy property
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Resets the object state.
    /// </summary>
    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())
{
     // Do something with the instance
}
// The instance is released back to the pool 

Alternatively, you could call TryRelease() to attempt to release the object back to the pool.

MyObject instance = pool.Acquire();
// Do something with the instance
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();

    // Acquire an instance from the pool
    using (A a1 = poolOfA.Acquire())
    {
        Diagnose("a1 acquired.", poolOfA);

        // Do something with the instance
    }

    // Here the object is disposed and released back to the pool.
    Diagnose("a1 disposed.", poolOfA);
}

static void Test2()
{
    Console.WriteLine();
    Console.WriteLine(" Test 2");
    Console.WriteLine(" ******");
    Console.WriteLine();

    // Attempt to leak memory by allocating two in a function and never releasing the objects
    // manually to the pool.
    AllocateMany();

    // We don't use them and we did not dispose them yet...
    Diagnose("function ended.", poolOfA);

    // Now, let's force GC to collect
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    Thread.Sleep(100);

    // How many now?
    Diagnose("GC collected.", poolOfA);

}

static void AllocateMany()
{
    // Acquire more instances...
    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.

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