Click here to Skip to main content
15,883,901 members
Articles / Programming Languages / C# 4.0

Concurrent Object Pool, the Right Way

Rate me:
Please Sign up or sign in to vote.
4.83/5 (12 votes)
22 Apr 2013CPOL2 min read 37.4K   1.1K   34   7
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. 

C#
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:

C#
var pool = new ConcurrentPool<MyObject>("Pool of MyObject"); 

If not, you can also provide a CreateInstanceDelegate to act as a constructor: 

C#
var pool = new ConcurrentPool<MyObject>("Pool of MyObject", () => new MyObject() ); 

Finally, acquiring and releasing the objects is done via a disposable pattern.

C#
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.

C#
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.

Image 1

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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Misakai Ltd.
Ireland Ireland
Roman Atachiants, Ph.D. is the architect behind emitter.io service, a real-time, low-latency publish/subscribe service for IoT, Gaming. He is a software engineer and scientist with extensive experience in different computer science domains, programming languages/principles/patterns & frameworks.

His main expertise consists of C# and .NET platform, game technologies, cloud, human-computer interaction, big data and artificial intelligence. He has an extensive programming knowledge and R&D expertise.



Comments and Discussions

 
GeneralMy vote of 5 Pin
D V L20-Feb-15 17:48
professionalD V L20-Feb-15 17:48 
GeneralMy vote of 5 Pin
Oleksandr Kulchytskyi22-Apr-13 21:09
professionalOleksandr Kulchytskyi22-Apr-13 21:09 
GeneralMy vote of 4 Pin
StianSandberg22-Apr-13 11:08
StianSandberg22-Apr-13 11:08 
QuestionWhat if the object is Disposed and then used? Pin
Paulo Zemek22-Apr-13 9:26
mvaPaulo Zemek22-Apr-13 9:26 
AnswerRe: What if the object is Disposed and then used? Pin
Kel_22-Apr-13 10:24
Kel_22-Apr-13 10:24 
GeneralRe: What if the object is Disposed and then used? Pin
Paulo Zemek22-Apr-13 10:26
mvaPaulo Zemek22-Apr-13 10:26 
GeneralRe: What if the object is Disposed and then used? Pin
Kel_22-Apr-13 10:39
Kel_22-Apr-13 10:39 

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.