How to: Create an Object Pool by Using a ConcurrentBag

This example shows how to use a concurrent bag to implement an object pool. Object pools can improve application performance in situations where you require multiple instances of a class and the class is expensive to create or destroy. When a client program requests a new object, the object pool first attempts to provide one that has already been created and returned to the pool. If none is available, only then is a new object created.

ConcurrentBag<T> is used to store the objects because it supports fast insertion and removal, especially when the same thread is both adding and removing items. This example could be further augmented to be built around a IProducerConsumerCollection<T>, which the bag data structure implements, as do ConcurrentQueue<T> and ConcurrentStack<T>.

Example

Imports System
Imports System.Collections.Concurrent
Imports System.Threading
Imports System.Threading.Tasks

Module ObjectPoolExample


    Public Class ObjectPool(Of T)
        Private _objects As ConcurrentBag(Of T)
        Private _objectGenerator As Func(Of T)

        Public Sub New(ByVal objectGenerator As Func(Of T))
            If objectGenerator Is Nothing Then Throw New ArgumentNullException("objectGenerator")
            _objects = New ConcurrentBag(Of T)()
            _objectGenerator = objectGenerator
        End Sub

        Public Function GetObject() As T
            Dim item As T
            If _objects.TryTake(item) Then Return item
            Return _objectGenerator()
        End Function

        Public Sub PutObject(ByVal item As T)
            _objects.Add(item)
        End Sub
    End Class


    Sub Main()

        Dim cts As CancellationTokenSource = New CancellationTokenSource()

        ' Create an opportunity for the user to cancel.
        Task.Factory.StartNew(Sub()
                                  If Console.ReadKey().KeyChar = "c"c Or Console.ReadKey().KeyChar = "C"c Then
                                      cts.Cancel()
                                  End If
                              End Sub)
        Dim pool As ObjectPool(Of TestClass) = New ObjectPool(Of TestClass)(Function() New TestClass())

        ' Create a high demand for TestClass objects.
        Parallel.For(0, 1000000, Sub(i, loopState)

                                     Dim mc As TestClass = pool.GetObject()
                                     Console.CursorLeft = 0
                                     ' This is the bottleneck in our application. All threads in this loop
                                     ' must serialize their access to the static Console class.
                                     Console.WriteLine("{0:####.####}", mc.GetValue(i))

                                     pool.PutObject(mc)
                                     If cts.Token.IsCancellationRequested Then
                                         loopState.Stop()

                                     End If
                                 End Sub)

        Console.WriteLine("Press the Enter key to exit.")
        Console.ReadLine()


    End Sub

    ' A toy class that requires some resources to create.
    ' You can experiment here to measure the performance of the
    ' object pool vs. ordinary instantiation.
    Class TestClass

        Public Nums() As Integer

        Public Function GetValue(ByVal i As Long) As Double
            Return Math.Sqrt(Nums(i))
        End Function

        Public Sub New()

            Nums = New Integer(10000000) {}
            '   ReDim Nums(1000000)
            Dim rand As Random = New Random()
            For i As Integer = 0 To Nums.Length - 1
                Nums(i) = rand.Next()
            Next
        End Sub
    End Class

End Module
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;


namespace ObjectPoolExample
{
    public class ObjectPool<T>
    {
        private ConcurrentBag<T> _objects;
        private Func<T> _objectGenerator;

        public ObjectPool(Func<T> objectGenerator)
        {
            if (objectGenerator == null) throw new ArgumentNullException("objectGenerator");
            _objects = new ConcurrentBag<T>();
            _objectGenerator = objectGenerator;
        }

        public T GetObject()
        {
            T item;
            if (_objects.TryTake(out item)) return item;
            return _objectGenerator();
        }

        public void PutObject(T item)
        {
            _objects.Add(item);
        }
    }

    class Program
    {
       static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();

            // Create an opportunity for the user to cancel.
            Task.Factory.StartNew(() =>
                {
                    if (Console.ReadKey().KeyChar == 'c' || Console.ReadKey().KeyChar == 'C')
                        cts.Cancel();
                });

            ObjectPool<MyClass> pool = new ObjectPool<MyClass> (() => new MyClass());            

            // Create a high demand for MyClass objects.
            Parallel.For(0, 1000000, (i, loopState) =>
                {
                    MyClass mc = pool.GetObject();
                    Console.CursorLeft = 0;
                    // This is the bottleneck in our application. All threads in this loop
                    // must serialize their access to the static Console class.
                    Console.WriteLine("{0:####.####}", mc.GetValue(i));                 

                    pool.PutObject(mc);
                    if (cts.Token.IsCancellationRequested)
                        loopState.Stop();                 

                });
            Console.WriteLine("Press the Enter key to exit.");
            Console.ReadLine();
        }

    }

    // A toy class that requires some resources to create.
    // You can experiment here to measure the performance of the
    // object pool vs. ordinary instantiation.
    class MyClass
    {
        public int[] Nums {get; set;}
        public double GetValue(long i)
        {
            return Math.Sqrt(Nums[i]);
        }
        public MyClass()
        {
            Nums = new int[1000000];
            Random rand = new Random();
            for (int i = 0; i < Nums.Length; i++)
                Nums[i] = rand.Next();
        }
    }   
}

See Also

Other Resources

Thread-Safe Collections