An object pool is a set of pre-initialized objects that your application can use, rather than creating and destroying all of the objects it needs. If the instantiation cost of an object type is high, your application might benefit from a pool of objects.
In this recipe, we are going to create an object pool based on ConcurrentStack
. ConcurrentStack
will handle concurrent access issues using fast interlocked operations, and will dispense our objects in a LIFO manner. We will also have an object pool client which creates three tasks. One creates objects and puts them in the pool, the other two tasks request objects from the pool on different threads.
Let's see how we can use ConcurrentStack
to build a pool of pre-initialized objects.
ObjectPool
as the Solution name.ObjectPool
project in the Solution Explorer and click on Add, then choose New Item. Select Visual C# Items, and Class. Enter ConcurrentObjectPool
as the name of the class.using
directives to the top of your ConcurrentObjectPool
class:using System; using System.Collections.Concurrent;
public class ConcurrentObjectPool<T> { }
ObjectPool
class is going to need a couple of private state fields. We need a ConcurrentStack
field which will provide our backing store and a Func<T>
field which will hold an object creation function the pool can use to generate objects when the pool is empty. Inside the class declaration, add the following fields:private ConcurrentStack<T> _objects; private Func<T> _objectInitializer;
ConcurrentObjectPool
class. The constructor should take a Func<T>
argument for the object generator and should instantiate a new ConcurrentStack
object as the backing store.public ConcurrentObjectPool(Func<T> objectInitializer) { _objects = new ConcurrentStack<T>(); _objectInitializer = objectInitializer; }
GetObject
method which will return a new object to the client. The GetObject
method will try to pop an object off the stack. If it can't pop one off the stack, it will use objectInitializer
to instantiate a new object.public T GetObject() { T item; if (_objects.TryPop(out item)) return item; return _objectInitializer(); }
PutObject
method that takes a generic item parameter and pushes it on the stack.public void PutObject(T item) { _objects.Push(item); }
Console
application that will use the object pool. Go back to Program.cs
and add the following using
directives at the top of the file:using System; using System.Text; using System.Threading.Tasks;
main
method of the program class, create a ConcurrentObjectPool
object and pass in a function that creates a new StringBuilder
object as the constructor parameter.var pool = new ConcurrentObjectPool<StringBuilder>(()=> new StringBuilder("Pooled Object created by objectInitializer"));
pool
using the PutObject
method.var task1 = Task.Factory.StartNew(() => { for (var index = 0; index < 10; index++) { StringBuilder newObject = new StringBuilder(string.Concat("Pooled object", index.ToString())); Console.WriteLine("Putting pooled object: {0}", index.ToString()); pool.PutObject(newObject); } });
pool
using the GetObject
method.task1.ContinueWith((antecedent)=> { for (var index = 0; index < 10; index++) { var pooledObject = pool.GetObject(); Console.WriteLine("First Task: {0}", pooledObject.ToString()); } }); task1.ContinueWith((antecedent) => { for (var index = 0; index < 10; index++) { var pooledObject = pool.GetObject(); Console.WriteLine("Second Tasks: {0}", pooledObject.ToString()); } });
There are other features we could have added to our object pool
, such as controlling the concurrency level and/or using thread local segments to store our objects, but this simple implementation does the job for our purposes.
The constructor of the object pool
takes a function argument that it can use to generate an object if the pool is empty, and stores the function in a private objectInitializer
field. We are just pooling StringBuilder
objects in this sample, so we passed in the following function:
()=>new StringBuilder("Pooled Object created by objectInitializer")
Our GetObject
method, which the client uses to get objects from pool
, just uses the TryPop
method of ConcurrentStack
to return an object. If TryPop
fails to return anything because the stack is empty, we just return the result of the objectInitializer
function.
public T GetObject() { T item; if (_objects.TryPop(out item)) return item; return _objectInitializer(); }
The PutObject
method probably doesn't require much explanation. It just uses the Push
method of ConcurrentStack
to push an object onto the stack.
Given that we chose to use ConcurrentStack
, our object references are returned in a LIFO fashion. We could have chosen another type of backing store if this didn't work for us. For example, we could have chosen to use ConcurrentQueue
as a backing store to have items returned in a
First-In-First-Out (FIFO) fashion, or we could have used ConcurrentBag
to provide unordered storage.
3.149.233.62