(The full set of ParallelExtensionsExtras Tour posts is available here.)
An object pool is a mechanism/pattern to avoid the repeated creation and destruction of objects. When code is done with an object, rather than allowing it to be garbage collected (and finalized if it’s finalizable), you put the object back into a special collection known as an object pool. Then, when you need an object, rather than always creating one, you ask the pool for one: if it has one, it gives it to you, otherwise it creates one and gives it to you. In many situations where creation and destruction is expensive, and where many objects are needed but where only a few at a time are needed, this can result in significant performance gains.
Object pools are just as relevant in multi-threaded scenarios as they are in single-threaded scenarios, but of course when dealing with multiple threads, you need to synchronize correctly (unless a separate pool is maintained per thread, in which case you’re trading synchronization cost for potentially creating more objects than you otherwise would). ParallelExtensionsExtras contains a simple ObjectPool<T> implementation in the ObjectPool.cs file, built on top of IProducerConsumerCollection<T>.
The implementation in ParallelExtensionsExtras is slightly more complex than what we’ll show here, which is just the basic essence of the type (the implementation in ParallelExtensionsExtras also implements IProducerConsumerCollection<T> and provides some useful additional helper methods).
public sealed class ObjectPool<T>
{
private readonly Func<T> _generator;
private readonly IProducerConsumerCollection<T> _objects;
public ObjectPool(Func<T> generator) :
this(generator, new ConcurrentQueue<T>()) { }
public ObjectPool(Func<T> generator,
IProducerConsumerCollection<T> collection)
{
if (generator == null)
throw new ArgumentNullException(“generator”);
if (collection == null)
throw new ArgumentNullException(“collection”);
_generator = generator;
_collection = collection;
}
public void PutObject(T item) { _objects.TryAdd(item); }
public T GetObject()
{
T value;
return _objects.TryTake(out value) ? value : _generator();
}
}
The constructors initialize the pool with the generator function to use for creating objects when the pool is empty as well as a collection used to store all of the pooled objects. The PutObject method simply adds the object into the collection. The GetObject method tries to take an object from the collection and return it, returning a newly generated object instead if nothing could be retrieved from the collection.
Note that an instance of this ObjectPool<T> type may be used from multiple threads concurrently, and all of the synchronization is handled automatically by the IProducerConsumerCollection<T> implementation.
0 comments