Created
April 23, 2016 15:25
-
-
Save cobysy/d62f1028ba90342ef6b808f44d7a1c35 to your computer and use it in GitHub Desktop.
Generic C# Resource Pool that manages a synchronized access of (theoretically) infinite callers of a restricted count of resource items.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ========================================================== | |
// 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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// =================================================================== | |
// 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); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment