-
-
Save mattwarren/28a0bedc717b5b66edfd to your computer and use it in GitHub Desktop.
Object Pools
This file contains 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
public interface IObjectPool<T> | |
{ | |
T Rent(); | |
void Return(T obj); | |
} | |
public interface IResetable | |
{ | |
void Reset(); | |
} | |
public class PooledObject<T> | |
{ | |
public T Object { get; } | |
public IObjectPool<PooledObject<T>> OwningPool { get; } | |
public bool IsRented { get; set; } | |
public PooledObject(T obj, IObjectPool<PooledObject<T>> owningPool) | |
{ | |
Object = obj; | |
OwningPool = owningPool; | |
} | |
} | |
public class CheckedPooledObject<T> : PooledObject<T>, IDisposable | |
{ | |
public CheckedPooledObject(T obj, IObjectPool<PooledObject<T>> owningPool) | |
: base(obj, owningPool) | |
{ | |
} | |
~CheckedPooledObject() | |
{ | |
throw new InvalidOperationException("Pooled object was not disposed"); | |
} | |
public void Dispose() | |
{ | |
GC.SuppressFinalize(this); | |
} | |
} | |
public class ObjectPoolObjectInUseException : Exception { } | |
public class ObjectPoolObjectReturnedTwiceException : Exception { } | |
public class ObjectPoolWrongPoolException : Exception { } | |
// No wrapping, no checking, no resetting | |
public class UnsafeObjectPool<T> : IObjectPool<T>, IDisposable | |
where T : class, new() | |
{ | |
private ConcurrentQueue<T> _queue = new ConcurrentQueue<T>(); | |
public T Rent() | |
{ | |
var queue = _queue; | |
if (queue == null) throw new ObjectDisposedException(nameof(UnsafeObjectPool<T>)); | |
T obj; | |
if (queue.TryDequeue(out obj)) | |
{ | |
return obj; | |
} | |
else | |
{ | |
return new T(); | |
} | |
} | |
public void Return(T obj) | |
{ | |
var queue = _queue; | |
if (queue != null) queue.Enqueue(obj); | |
} | |
public void Dispose() | |
{ | |
_queue = null; | |
} | |
} | |
// Common or garden object pool | |
public class ObjectPool<T> : IObjectPool<PooledObject<T>>, IDisposable | |
where T : class, IResetable, new() | |
{ | |
private ConcurrentQueue<PooledObject<T>> _queue = new ConcurrentQueue<PooledObject<T>>(); | |
public PooledObject<T> Rent() | |
{ | |
var queue = _queue; | |
if (queue == null) throw new ObjectDisposedException(nameof(ObjectPool<T>)); | |
PooledObject<T> pooledObject; | |
if (queue.TryDequeue(out pooledObject)) | |
{ | |
if (pooledObject.IsRented) throw new ObjectPoolObjectInUseException(); | |
pooledObject.IsRented = true; | |
return pooledObject; | |
} | |
else | |
{ | |
return new PooledObject<T>(new T(), this) { IsRented = true }; | |
} | |
} | |
public void Return(PooledObject<T> pooledObject) | |
{ | |
if (!pooledObject.IsRented) throw new ObjectPoolObjectReturnedTwiceException(); | |
if (pooledObject.OwningPool != this) throw new ObjectPoolWrongPoolException(); | |
pooledObject.Object.Reset(); | |
pooledObject.IsRented = false; | |
var queue = _queue; | |
if (queue != null) queue.Enqueue(pooledObject); | |
} | |
public void Dispose() | |
{ | |
var queue = _queue; | |
_queue = null; | |
PooledObject<T> pooledObject; | |
while (queue.TryDequeue(out pooledObject)) | |
{ | |
// Empty pool | |
} | |
} | |
} | |
// Dispose pooled objects | |
public class DisposingObjectPool<T> : IObjectPool<PooledObject<T>>, IDisposable | |
where T : class, IResetable, IDisposable, new() | |
{ | |
private ConcurrentQueue<PooledObject<T>> _queue = new ConcurrentQueue<PooledObject<T>>(); | |
public PooledObject<T> Rent() | |
{ | |
var queue = _queue; | |
if (queue == null) throw new ObjectDisposedException(nameof(ObjectPool<T>)); | |
PooledObject<T> pooledObject; | |
if (queue.TryDequeue(out pooledObject)) | |
{ | |
if (pooledObject.IsRented) throw new ObjectPoolObjectInUseException(); | |
pooledObject.IsRented = true; | |
return pooledObject; | |
} | |
else | |
{ | |
return new PooledObject<T>(new T(), this) { IsRented = true }; | |
} | |
} | |
public void Return(PooledObject<T> pooledObject) | |
{ | |
if (!pooledObject.IsRented) throw new ObjectPoolObjectReturnedTwiceException(); | |
if (pooledObject.OwningPool != this) throw new ObjectPoolWrongPoolException(); | |
pooledObject.Object.Reset(); | |
pooledObject.IsRented = false; | |
Interlocked.MemoryBarrier(); | |
var queue = _queue; | |
if (queue != null) | |
{ | |
queue.Enqueue(pooledObject); | |
if (_queue != null) | |
{ | |
pooledObject.Object.Dispose(); | |
} | |
} | |
else | |
{ | |
pooledObject.Object.Dispose(); | |
} | |
} | |
public void Dispose() | |
{ | |
var queue = _queue; | |
_queue = null; | |
Interlocked.MemoryBarrier(); | |
PooledObject<T> pooledObject; | |
while (queue.TryDequeue(out pooledObject)) | |
{ | |
pooledObject.Object.Dispose(); | |
} | |
} | |
} | |
// Max pooled objects | |
public class BoundedObjectPool<T> : IObjectPool<PooledObject<T>>, IDisposable | |
where T : class, IResetable, IDisposable, new() | |
{ | |
private ConcurrentQueue<PooledObject<T>> _queue = new ConcurrentQueue<PooledObject<T>>(); | |
private int _maxPooled; | |
public BoundedObjectPool(int maxPooled) | |
{ | |
_maxPooled = maxPooled; | |
} | |
public PooledObject<T> Rent() | |
{ | |
var queue = _queue; | |
if (queue == null) throw new ObjectDisposedException(nameof(ObjectPool<T>)); | |
PooledObject<T> pooledObject; | |
if (queue.TryDequeue(out pooledObject)) | |
{ | |
if (pooledObject.IsRented) throw new ObjectPoolObjectInUseException(); | |
pooledObject.IsRented = true; | |
return pooledObject; | |
} | |
else | |
{ | |
return new PooledObject<T>(new T(), this) { IsRented = true }; | |
} | |
} | |
public void Return(PooledObject<T> pooledObject) | |
{ | |
if (!pooledObject.IsRented) throw new ObjectPoolObjectReturnedTwiceException(); | |
if (pooledObject.OwningPool != this) throw new ObjectPoolWrongPoolException(); | |
pooledObject.Object.Reset(); | |
pooledObject.IsRented = false; | |
Interlocked.MemoryBarrier(); | |
var queue = _queue; | |
if (queue != null && queue.Count < _maxPooled) | |
{ | |
queue.Enqueue(pooledObject); | |
if (_queue != null) | |
{ | |
pooledObject.Object.Dispose(); | |
} | |
} | |
else | |
{ | |
pooledObject.Object.Dispose(); | |
} | |
} | |
public void Dispose() | |
{ | |
var queue = _queue; | |
_queue = null; | |
Interlocked.MemoryBarrier(); | |
PooledObject<T> pooledObject; | |
while (queue.TryDequeue(out pooledObject)) | |
{ | |
pooledObject.Object.Dispose(); | |
} | |
} | |
} | |
// Factory constructor | |
public class FactoryObjectPool<T> : IObjectPool<PooledObject<T>>, IDisposable | |
where T : class, IResetable, IDisposable | |
{ | |
private ConcurrentQueue<PooledObject<T>> _queue = new ConcurrentQueue<PooledObject<T>>(); | |
private int _maxPooled; | |
private Func<T> _factory; | |
public FactoryObjectPool(int maxPooled, Func<T> factory) | |
{ | |
_maxPooled = maxPooled; | |
_factory = factory; | |
} | |
public PooledObject<T> Rent() | |
{ | |
var queue = _queue; | |
if (queue == null) throw new ObjectDisposedException(nameof(FactoryObjectPool<T>)); | |
PooledObject<T> pooledObject; | |
if (queue.TryDequeue(out pooledObject)) | |
{ | |
if (pooledObject.IsRented) throw new ObjectPoolObjectInUseException(); | |
pooledObject.IsRented = true; | |
return pooledObject; | |
} | |
else | |
{ | |
return new PooledObject<T>(_factory(), this) { IsRented = true }; | |
} | |
} | |
public void Return(PooledObject<T> pooledObject) | |
{ | |
if (!pooledObject.IsRented) throw new ObjectPoolObjectReturnedTwiceException(); | |
if (pooledObject.OwningPool != this) throw new ObjectPoolWrongPoolException(); | |
pooledObject.Object.Reset(); | |
pooledObject.IsRented = false; | |
Interlocked.MemoryBarrier(); | |
var queue = _queue; | |
if (queue != null && queue.Count < _maxPooled) | |
{ | |
queue.Enqueue(pooledObject); | |
if (_queue != null) | |
{ | |
pooledObject.Object.Dispose(); | |
} | |
} | |
else | |
{ | |
pooledObject.Object.Dispose(); | |
} | |
} | |
public void Dispose() | |
{ | |
var queue = _queue; | |
_queue = null; | |
Interlocked.MemoryBarrier(); | |
PooledObject<T> pooledObject; | |
while (queue.TryDequeue(out pooledObject)) | |
{ | |
pooledObject.Object.Dispose(); | |
} | |
} | |
} | |
// Test pools | |
public class CheckedObjectPool<T> : IObjectPool<PooledObject<T>>, IDisposable | |
where T : class, IResetable, IDisposable, new() | |
{ | |
private ConcurrentQueue<CheckedPooledObject<T>> _queue = new ConcurrentQueue<CheckedPooledObject<T>>(); | |
public PooledObject<T> Rent() | |
{ | |
var queue = _queue; | |
if (queue == null) throw new ObjectDisposedException(nameof(ObjectPool<T>)); | |
CheckedPooledObject<T> pooledObject; | |
if (queue.TryDequeue(out pooledObject)) | |
{ | |
if (pooledObject.IsRented) throw new ObjectPoolObjectInUseException(); | |
pooledObject.IsRented = true; | |
return pooledObject; | |
} | |
else | |
{ | |
return new CheckedPooledObject<T>(new T(), this) { IsRented = true }; | |
} | |
} | |
public void Return(PooledObject<T> pooledObject) | |
{ | |
if (!pooledObject.IsRented) throw new ObjectPoolObjectReturnedTwiceException(); | |
if (pooledObject.OwningPool != this) throw new ObjectPoolWrongPoolException(); | |
CheckedPooledObject<T> checkedPooledObject = pooledObject as CheckedPooledObject<T>; | |
if (checkedPooledObject == null) | |
{ | |
throw new ArgumentException("Pooled object was not created by this pool", nameof(pooledObject)); | |
} | |
checkedPooledObject.Object.Reset(); | |
checkedPooledObject.IsRented = false; | |
Interlocked.MemoryBarrier(); | |
var queue = _queue; | |
if (queue != null) | |
{ | |
queue.Enqueue(checkedPooledObject); | |
if (_queue != null) | |
{ | |
checkedPooledObject.Object.Dispose(); | |
} | |
} | |
else | |
{ | |
checkedPooledObject.Object.Dispose(); | |
} | |
} | |
public void Dispose() | |
{ | |
var queue = _queue; | |
_queue = null; | |
Interlocked.MemoryBarrier(); | |
CheckedPooledObject<T> checkedPooledObject; | |
while (queue.TryDequeue(out checkedPooledObject)) | |
{ | |
checkedPooledObject.Object.Dispose(); | |
} | |
} | |
} | |
// No actual pooling | |
public class NoPoolFactoryObjectPool<T> : IObjectPool<T>, IDisposable | |
where T : class, IResetable, new() | |
{ | |
private Func<T> _factory; | |
private bool _isDisposed; | |
public NoPoolFactoryObjectPool(Func<T> factory) | |
{ | |
_factory = factory; | |
} | |
public T Rent() | |
{ | |
if (_isDisposed) throw new ObjectDisposedException(nameof(ObjectPool<T>)); | |
return _factory(); | |
} | |
public void Return(T obj) | |
{ | |
obj.Reset(); | |
} | |
public void Dispose() | |
{ | |
_isDisposed = true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment