Definition
A resource pool is a class that manages a synchronized access of (theoretically) infinite callers of a restricted count of resource items.One reason to use a resource pool could be a integrated sub system that is restricted by its maximum count of concurrent accessing threads. This could be a web service with a restricted count of sessions. Another reason for a resource pool is a resource that becomes working slow when it becomes accessed by to many concurrent connections. This could be a web-service, a database or a farm of processing servers. A third reason for a resource pool could be a resource that takes long to become initialized, but can be reused. This could be database connections, XSLT transformation classes, custom XML serializers or many other classes.
Some well known .NET framework implementations of resource pools are:
- The ADO.NET connection pooling mechanism
- The System.Reflection.TheadPool class
Implementation
A few month ago I wrote myself a generic resource pool class that helps me to get those kinds of bottle necks managed.Here is my implementation of a generic, thread safe C# resource pool. All source code files, as well as some NUnit tests, are also attached as downloadable files at bottom of this post.
// =================================================================== // PoolItem<T> // =================================================================== /// <summary> /// Represents an item in the <see cref="ResourcePool{T}"/> /// </summary> /// <typeparam name="T">The type of the resource to be hold</typeparam> public sealed class PoolItem<T> : IDisposable where T : class { internal PoolItem(ResourcePool<T> pool, T resource) { _pool = pool; _resource = resource; } private T _resource; private readonly ResourcePool<T> _pool; public static implicit operator T(PoolItem<T> item) { return item.Resource; } /// <summary> /// Gets the resource hold by this resource pool item /// </summary> public T Resource { get { return _resource; } } /// <summary> /// Disposes this instance of an resource pool item and sends the resource back /// to the pool /// </summary> public void Dispose() { _pool.SendBackToPool(_resource); _resource = null; } } // =================================================================== // ResourcePool<T> // =================================================================== /// <summary> /// Generic pool class /// </summary> /// <typeparam name="T">The type of items to be stored in the pool</typeparam> public class ResourcePool<T> : IDisposable where T : class { /// <summary> /// Creates a new pool /// </summary> /// <param name="factory">The factory method to create new items to /// be stored in the pool</param> public ResourcePool(Func<ResourcePool<T>, T> factory) { if (factory == null) throw new ArgumentNullException("factory"); _factoryMethod = factory; } private readonly Func<ResourcePool<T>, T> _factoryMethod; private ConcurrentQueue<PoolItem<T>> _freeItems = new ConcurrentQueue<PoolItem<T>>(); private ConcurrentQueue<AutoResetEvent> _waitLocks = new ConcurrentQueue<AutoResetEvent>(); private ConcurrentDictionary<AutoResetEvent, PoolItem<T>> _syncContext = new ConcurrentDictionary<AutoResetEvent, PoolItem<T>>(); public Action<T> CleanupPoolItem { get; set; } /// <summary> /// Gets the current count of items in the pool /// </summary> public int Count { get; private set; } public void Dispose() { lock (this) { if (Count != _freeItems.Count) throw new InvalidOperationException( "Cannot dispose the resource pool while one or more pooled " + "items are in use"); foreach (var poolItem in _freeItems) { Action<T> cleanMethod = CleanupPoolItem; if (cleanMethod != null) CleanupPoolItem(poolItem.Resource); } Count = 0; _freeItems = null; _waitLocks = null; _syncContext = null; } } /// <summary> /// Gets a free resource from the pool. If no free items available this method /// tries to create a new item. If no new item could be created this method /// waits until another thread frees one resource. /// </summary> /// <returns>A resource item</returns> public PoolItem<T> GetItem() { PoolItem<T> item; // try to get an item if (!TryGetItem(out item)) { AutoResetEvent waitLock = null; lock (this) { // try to get an entry in exclusive mode if (!TryGetItem(out item)) { // no item available, create a wait lock and enqueue it waitLock = new AutoResetEvent(false); _waitLocks.Enqueue(waitLock); } } if (waitLock != null) { // wait until a new item is available waitLock.WaitOne(); _syncContext.TryRemove(waitLock, out item); waitLock.Dispose(); } } return item; } private bool TryGetItem(out PoolItem<T> item) { // try to get an already pooled resource if (_freeItems.TryDequeue(out item)) return true; lock (this) { // try to create a new resource T resource = _factoryMethod(this); if (resource == null && Count == 0) throw new InvalidOperationException("Pool empty and no item created"); if (resource != null) { // a new resource was created and can be returned Count++; item = new PoolItem<T>(this, resource); } else { // no items available to return at the moment item = null; } return item != null; } } /// <summary> /// Called from <see cref="PoolItem{T}"/> to free previously taked resources /// </summary> /// <param name="resource">The resource to send back into the pool.</param> internal void SendBackToPool(T resource) { lock (this) { PoolItem<T> item = new PoolItem<T>(this, resource); AutoResetEvent waitLock; if (_waitLocks.TryDequeue(out waitLock)) { _syncContext.TryAdd(waitLock, item); waitLock.Set(); } else { _freeItems.Enqueue(item); } } } }
As you see, the ResourcePool<T> returns PoolItem<T> objects that hold a reference of the pooled resources. The pool does not need to become initialized with any resource items, new resource items become lazy initialized when (if) needed. Therefore the pool requires a factory method provided at construction. This method becomes called whenever a new resource is requested and no free resources are currently available. If the factory method returns null the requesting thread becomes suspended until another thread releases a used resource.
Usage
The pool can be used out of the box.class MyResource { public void DoSomething() { } } [Test] public void SampleUsage() { ResourcePool<MyResource> pool = new ResourcePool<MyResource>(CreateResource); using (var poolItem = pool.GetItem()) { MyResource resource = poolItem.Resource; resource.DoSomething(); } } private static MyResource CreateResource(ResourcePool<MyResource> pool) { return pool.Count < 3 ? new MyResource() : null; }
However, if used at many positions in your system I'd suggest to wrap it into a custom pool class. This pool can return a wrapper of the real resources that provide a more specific interface to the consumer. This wrapper can hold an internal reference to a PoolItem<T> and implement IDisposable to free the resources back into the pool when not needed anymore.
// ========================================================== // The real resource class InternalResource { public void DoSomething() { } } // ========================================================== // The external wrapper returned to the consumer class MyResource : IDisposable { private PoolItem<InternalResource> _poolItem; public MyResource(PoolItem<InternalResource> poolItem) { _poolItem = poolItem; } public void DoSomething() { _poolItem.Resource.DoSomething(); } public void Dispose() { _poolItem.Dispose(); _poolItem = null; } } // ========================================================== // The custom pool class MyPool { private ResourcePool<InternalResource> _pool; public MyPool() { _pool = new ResourcePool<InternalResource>(CreateResource); } public MyResource GetItem() { return new MyResource(_pool.GetItem()); } private InternalResource CreateResource(ResourcePool<InternalResource> pool) { return pool.Count < 3 ? new InternalResource() : null; } } // ========================================================== // Sample usage [Test] public void SampleUsage() { MyPool pool = new MyPool(); using (MyResource resource = pool.GetItem()) { resource.DoSomething(); } }
Here are the files for download:
I hope the pool can help you to solve a few of your issues, as it did to me.
This is an Excellent implementation and has really helped us get over the connection issues .
ReplyDelete