From 72aa9fdd5c2394f6d826eb0580c14d85d14ce29c Mon Sep 17 00:00:00 2001 From: Alessio Parma Date: Fri, 7 Apr 2017 21:43:44 +0200 Subject: [PATCH] reworking pooled object events --- .../CodeProject.ObjectPool.csproj | 4 - .../Core/ErrorMessages.cs | 2 +- src/CodeProject.ObjectPool/ObjectPool.cs | 124 ++++++++++-------- .../ParameterizedObjectPool.cs | 4 +- src/CodeProject.ObjectPool/PooledObject.cs | 76 +++++------ .../PooledObjectWrapper.cs | 27 +--- .../Specialized/PooledMemoryStream.cs | 62 ++++----- .../Specialized/PooledStringBuilder.cs | 40 +++--- src/CodeProject.ObjectPool/TimedObjectPool.cs | 81 +++++++++++- .../Program.cs | 27 ++-- 10 files changed, 255 insertions(+), 192 deletions(-) diff --git a/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj b/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj index 52d66f9..955a583 100644 --- a/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj +++ b/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj @@ -80,8 +80,4 @@ - - - - \ No newline at end of file diff --git a/src/CodeProject.ObjectPool/Core/ErrorMessages.cs b/src/CodeProject.ObjectPool/Core/ErrorMessages.cs index 0115b6d..9800be3 100644 --- a/src/CodeProject.ObjectPool/Core/ErrorMessages.cs +++ b/src/CodeProject.ObjectPool/Core/ErrorMessages.cs @@ -29,7 +29,7 @@ namespace CodeProject.ObjectPool.Core internal static class ErrorMessages { public const string NegativeOrZeroMaximumPoolSize = "Maximum pool size must be greater than zero."; - public const string NullDiagnostics = "Pool diagnostics recorder cannot be null."; + public const string NegativeOrZeroTimeout = "Timeout must be greater than zero."; public const string NullResource = "Resource cannot be null."; } } \ No newline at end of file diff --git a/src/CodeProject.ObjectPool/ObjectPool.cs b/src/CodeProject.ObjectPool/ObjectPool.cs index fdd1e94..bbbab1b 100644 --- a/src/CodeProject.ObjectPool/ObjectPool.cs +++ b/src/CodeProject.ObjectPool/ObjectPool.cs @@ -37,55 +37,6 @@ public static class ObjectPool public class ObjectPool : IObjectPool, IObjectPoolHandle where T : PooledObject { - #region Public Properties - - /// - /// Gets the Diagnostics class for the current Object Pool, whose goal is to record data - /// about how the pool operates. By default, however, an object pool records anything; you - /// have to enable it through the property. - /// - public ObjectPoolDiagnostics Diagnostics { get; set; } - - /// - /// Gets the Factory method that will be used for creating new objects. - /// - public Func FactoryMethod { get; protected set; } - - /// - /// Gets or sets the maximum number of objects that could be available at the same time in - /// the pool. - /// - public int MaximumPoolSize - { - get - { - return PooledObjects.Capacity; - } - set - { - // Preconditions - Raise.ArgumentOutOfRangeException.If(value < 1, nameof(value), ErrorMessages.NegativeOrZeroMaximumPoolSize); - - // Resize the pool and destroy exceeding items, if any. - foreach (var exceedingItem in PooledObjects.Resize(value)) - { - DestroyPooledObject(exceedingItem); - } - } - } - - /// - /// Gets the count of the objects currently in the pool. - /// - public int ObjectsInPoolCount => PooledObjects.Count; - - /// - /// The concurrent buffer containing pooled objects. - /// - protected PooledObjectBuffer PooledObjects { get; } = new PooledObjectBuffer(); - - #endregion Public Properties - #region C'tor and Initialization code /// @@ -99,7 +50,7 @@ public ObjectPool() /// /// Initializes a new pool with specified maximum pool size. /// - /// The maximum pool size limit + /// The maximum pool size limit. /// /// is less than or equal to zero. /// @@ -120,7 +71,7 @@ public ObjectPool(Func factoryMethod) /// /// Initializes a new pool with specified factory method and maximum size. /// - /// The maximum pool size limit + /// The maximum pool size limit. /// The factory method that will be used to create new objects. /// /// is less than or equal to zero. @@ -128,10 +79,14 @@ public ObjectPool(Func factoryMethod) public ObjectPool(int maximumPoolSize, Func factoryMethod) { // Preconditions - Raise.ArgumentOutOfRangeException.If(maximumPoolSize < 1, nameof(maximumPoolSize), ErrorMessages.NegativeOrZeroMaximumPoolSize); + Raise.ArgumentOutOfRangeException.IfIsLessOrEqual(maximumPoolSize, 0, nameof(maximumPoolSize), ErrorMessages.NegativeOrZeroMaximumPoolSize); - // Assigning properties. - FactoryMethod = factoryMethod; + // Throws an exception if the type does not have default constructor - on purpose! We + // could have added a generic constraint with new (), but we did not want to limit the + // user and force a parameterless constructor. + FactoryMethod = factoryMethod ?? Activator.CreateInstance; + + // Max pool size. MaximumPoolSize = maximumPoolSize; // Creating a new instance for the Diagnostics class. @@ -140,6 +95,55 @@ public ObjectPool(int maximumPoolSize, Func factoryMethod) #endregion C'tor and Initialization code + #region Public Properties + + /// + /// Gets the Diagnostics class for the current Object Pool, whose goal is to record data + /// about how the pool operates. By default, however, an object pool records anything; you + /// have to enable it through the property. + /// + public ObjectPoolDiagnostics Diagnostics { get; set; } + + /// + /// Gets the Factory method that will be used for creating new objects. + /// + public Func FactoryMethod { get; protected set; } + + /// + /// Gets or sets the maximum number of objects that could be available at the same time in + /// the pool. + /// + public int MaximumPoolSize + { + get + { + return PooledObjects.Capacity; + } + set + { + // Preconditions + Raise.ArgumentOutOfRangeException.If(value < 1, nameof(value), ErrorMessages.NegativeOrZeroMaximumPoolSize); + + // Resize the pool and destroy exceeding items, if any. + foreach (var exceedingItem in PooledObjects.Resize(value)) + { + DestroyPooledObject(exceedingItem); + } + } + } + + /// + /// Gets the count of the objects currently in the pool. + /// + public int ObjectsInPoolCount => PooledObjects.Count; + + /// + /// The concurrent buffer containing pooled objects. + /// + protected PooledObjectBuffer PooledObjects { get; } = new PooledObjectBuffer(); + + #endregion Public Properties + #region Finalizer /// @@ -263,15 +267,19 @@ protected T CreatePooledObject() Diagnostics.IncrementObjectsCreatedCount(); } - // Throws an exception if the type does not have default constructor - on purpose! We - // could have added a generic constraint with new (), but we did not want to limit the - // user and force a parameterless constructor. - var newObject = FactoryMethod?.Invoke() ?? Activator.CreateInstance(); + if (FactoryMethod == null) + { + // A child class has deleted our factory method. Therefore, we can only return null. + return null; + } + + var newObject = FactoryMethod(); // Setting the 'return to pool' action and other properties in the newly created pooled object. newObject.PooledObjectInfo.Id = Interlocked.Increment(ref _lastPooledObjectId); newObject.PooledObjectInfo.State = PooledObjectState.Available; newObject.PooledObjectInfo.Handle = this; + return newObject; } diff --git a/src/CodeProject.ObjectPool/ParameterizedObjectPool.cs b/src/CodeProject.ObjectPool/ParameterizedObjectPool.cs index 1b9ee4a..52b4f3d 100644 --- a/src/CodeProject.ObjectPool/ParameterizedObjectPool.cs +++ b/src/CodeProject.ObjectPool/ParameterizedObjectPool.cs @@ -100,7 +100,7 @@ public ParameterizedObjectPool() /// /// Initializes a new pool with specified maximum pool size. /// - /// The maximum pool size limit + /// The maximum pool size limit. public ParameterizedObjectPool(int maximumPoolSize) : this(maximumPoolSize, null) { @@ -118,7 +118,7 @@ public ParameterizedObjectPool(Func factoryMethod) /// /// Initializes a new pool with specified factory method and maximum size. /// - /// The maximum pool size limit + /// The maximum pool size limit. /// The factory method that will be used to create new objects. public ParameterizedObjectPool(int maximumPoolSize, Func factoryMethod) { diff --git a/src/CodeProject.ObjectPool/PooledObject.cs b/src/CodeProject.ObjectPool/PooledObject.cs index f4abd6c..1ceb2b4 100644 --- a/src/CodeProject.ObjectPool/PooledObject.cs +++ b/src/CodeProject.ObjectPool/PooledObject.cs @@ -55,21 +55,24 @@ internal bool ReleaseResources() { var successFlag = true; - try - { - OnReleaseResources(); - } - catch (Exception ex) + if (OnReleaseResources != null) { -#if !NET35 - if (Log.IsWarnEnabled()) + try { - Log.WarnException("[ObjectPool] An unexpected error occurred while releasing resources", ex); + OnReleaseResources(); } + catch (Exception ex) + { +#if !NET35 + if (Log.IsWarnEnabled()) + { + Log.WarnException("[ObjectPool] An unexpected error occurred while releasing resources", ex); + } #else - System.Diagnostics.Debug.Assert(ex != null); // Placeholder to avoid warnings + System.Diagnostics.Debug.Assert(ex != null); // Placeholder to avoid warnings #endif - successFlag = false; + successFlag = false; + } } return successFlag; @@ -83,33 +86,36 @@ internal bool ResetState() { var successFlag = true; - try + if (OnResetState != null) { - OnResetState(); - } - catch (CannotResetStateException crsex) - { -#if !NET35 - if (Log.IsDebugEnabled()) + try { - Log.DebugException("[ObjectPool] Object state could not be reset", crsex); + OnResetState(); } + catch (CannotResetStateException crsex) + { +#if !NET35 + if (Log.IsDebugEnabled()) + { + Log.DebugException("[ObjectPool] Object state could not be reset", crsex); + } #else - System.Diagnostics.Debug.Assert(crsex != null); // Placeholder to avoid warnings + System.Diagnostics.Debug.Assert(crsex != null); // Placeholder to avoid warnings #endif - successFlag = false; - } - catch (Exception ex) - { -#if !NET35 - if (Log.IsWarnEnabled()) - { - Log.WarnException("[ObjectPool] An unexpected error occurred while resetting state", ex); + successFlag = false; } + catch (Exception ex) + { +#if !NET35 + if (Log.IsWarnEnabled()) + { + Log.WarnException("[ObjectPool] An unexpected error occurred while resetting state", ex); + } #else - System.Diagnostics.Debug.Assert(ex != null); // Placeholder to avoid warnings + System.Diagnostics.Debug.Assert(ex != null); // Placeholder to avoid warnings #endif - successFlag = false; + successFlag = false; + } } return successFlag; @@ -117,23 +123,19 @@ internal bool ResetState() #endregion Internal Methods - resource and state management - #region Virtual Template Methods - extending resource and state management + #region Events - extending resource and state management /// /// Reset the object state to allow this object to be re-used by other parts of the application. /// - protected virtual void OnResetState() - { - } + public Action OnResetState { get; set; } /// /// Releases the object's resources. /// - protected virtual void OnReleaseResources() - { - } + public Action OnReleaseResources { get; set; } - #endregion Virtual Template Methods - extending resource and state management + #endregion Events - extending resource and state management #region Returning object to pool - Dispose and Finalizer diff --git a/src/CodeProject.ObjectPool/PooledObjectWrapper.cs b/src/CodeProject.ObjectPool/PooledObjectWrapper.cs index 48897d3..9c1ce86 100644 --- a/src/CodeProject.ObjectPool/PooledObjectWrapper.cs +++ b/src/CodeProject.ObjectPool/PooledObjectWrapper.cs @@ -31,17 +31,10 @@ public PooledObjectWrapper(T resource) Raise.ArgumentNullException.IfIsNull(resource, nameof(resource), ErrorMessages.NullResource); InternalResource = resource; - } - - /// - /// Triggered by the pool manager when there is no need for this object anymore. - /// - public Action WrapperReleaseResourcesAction { get; set; } - /// - /// Triggered by the pool manager just before the object is being returned to the pool. - /// - public Action WrapperResetStateAction { get; set; } + base.OnReleaseResources += () => OnReleaseResources?.Invoke(InternalResource); + base.OnResetState += () => OnResetState?.Invoke(InternalResource); + } /// /// The resource wrapped inside this class. @@ -49,19 +42,13 @@ public PooledObjectWrapper(T resource) public T InternalResource { get; } /// - /// Triggers the , if any. + /// Triggered by the pool manager when there is no need for this object anymore. /// - protected override void OnReleaseResources() - { - WrapperReleaseResourcesAction?.Invoke(InternalResource); - } + public new Action OnReleaseResources { get; set; } /// - /// Triggers the , if any. + /// Triggered by the pool manager just before the object is being returned to the pool. /// - protected override void OnResetState() - { - WrapperResetStateAction?.Invoke(InternalResource); - } + public new Action OnResetState { get; set; } } } \ No newline at end of file diff --git a/src/CodeProject.ObjectPool/Specialized/PooledMemoryStream.cs b/src/CodeProject.ObjectPool/Specialized/PooledMemoryStream.cs index 1b26ec9..bde80d6 100644 --- a/src/CodeProject.ObjectPool/Specialized/PooledMemoryStream.cs +++ b/src/CodeProject.ObjectPool/Specialized/PooledMemoryStream.cs @@ -47,6 +47,33 @@ public PooledMemoryStream(int capacity) { Parent = this }; + + OnResetState += () => + { + if (!_trackedMemoryStream.CanRead || !_trackedMemoryStream.CanWrite || !_trackedMemoryStream.CanSeek) + { + throw new CannotResetStateException($"Memory stream has already been disposed"); + } + + var memoryStreamPool = PooledObjectInfo.Handle as IMemoryStreamPool; + if (_trackedMemoryStream.Capacity < memoryStreamPool.MinimumMemoryStreamCapacity) + { + throw new CannotResetStateException($"Memory stream capacity is {_trackedMemoryStream.Capacity}, while minimum required capacity is {memoryStreamPool.MinimumMemoryStreamCapacity}"); + } + if (_trackedMemoryStream.Capacity > memoryStreamPool.MaximumMemoryStreamCapacity) + { + throw new CannotResetStateException($"Memory stream capacity is {_trackedMemoryStream.Capacity}, while maximum allowed capacity is {memoryStreamPool.MaximumMemoryStreamCapacity}"); + } + + _trackedMemoryStream.Position = 0L; + _trackedMemoryStream.SetLength(0L); + }; + + OnReleaseResources += () => + { + _trackedMemoryStream.Parent = null; + _trackedMemoryStream.Dispose(); + }; } /// @@ -60,41 +87,6 @@ public PooledMemoryStream(int capacity) /// A string that represents the current object. public override string ToString() => _trackedMemoryStream.ToString(); - /// - /// Reset the object state to allow this object to be re-used by other parts of the application. - /// - protected override void OnResetState() - { - if (!_trackedMemoryStream.CanRead || !_trackedMemoryStream.CanWrite || !_trackedMemoryStream.CanSeek) - { - throw new CannotResetStateException($"Memory stream has already been disposed"); - } - - var memoryStreamPool = PooledObjectInfo.Handle as IMemoryStreamPool; - if (_trackedMemoryStream.Capacity < memoryStreamPool.MinimumMemoryStreamCapacity) - { - throw new CannotResetStateException($"Memory stream capacity is {_trackedMemoryStream.Capacity}, while minimum required capacity is {memoryStreamPool.MinimumMemoryStreamCapacity}"); - } - if (_trackedMemoryStream.Capacity > memoryStreamPool.MaximumMemoryStreamCapacity) - { - throw new CannotResetStateException($"Memory stream capacity is {_trackedMemoryStream.Capacity}, while maximum allowed capacity is {memoryStreamPool.MaximumMemoryStreamCapacity}"); - } - - _trackedMemoryStream.Position = 0L; - _trackedMemoryStream.SetLength(0L); - base.OnResetState(); - } - - /// - /// Releases the object's resources. - /// - protected override void OnReleaseResources() - { - _trackedMemoryStream.Parent = null; - _trackedMemoryStream.Dispose(); - base.OnReleaseResources(); - } - private sealed class TrackedMemoryStream : MemoryStream { public TrackedMemoryStream(int capacity) diff --git a/src/CodeProject.ObjectPool/Specialized/PooledStringBuilder.cs b/src/CodeProject.ObjectPool/Specialized/PooledStringBuilder.cs index 6c43ac6..755fbb6 100644 --- a/src/CodeProject.ObjectPool/Specialized/PooledStringBuilder.cs +++ b/src/CodeProject.ObjectPool/Specialized/PooledStringBuilder.cs @@ -44,37 +44,29 @@ public class PooledStringBuilder : PooledObject public PooledStringBuilder(int capacity) { StringBuilder = new StringBuilder(capacity); - } - /// - /// Returns a string that represents the current object. - /// - /// A string that represents the current object. - public override string ToString() => StringBuilder.ToString(); - - /// - /// Reset the object state to allow this object to be re-used by other parts of the application. - /// - protected override void OnResetState() - { - var stringBuilderPool = PooledObjectInfo.Handle as IStringBuilderPool; - if (StringBuilder.Capacity > stringBuilderPool.MaximumStringBuilderCapacity) + OnResetState += () => { - throw new CannotResetStateException($"String builder capacity is {StringBuilder.Capacity}, while maximum allowed capacity is {stringBuilderPool.MaximumStringBuilderCapacity}"); - } + var stringBuilderPool = PooledObjectInfo.Handle as IStringBuilderPool; + if (StringBuilder.Capacity > stringBuilderPool.MaximumStringBuilderCapacity) + { + throw new CannotResetStateException($"String builder capacity is {StringBuilder.Capacity}, while maximum allowed capacity is {stringBuilderPool.MaximumStringBuilderCapacity}"); + } + + ClearStringBuilder(); + }; - ClearStringBuilder(); - base.OnResetState(); + OnReleaseResources += () => + { + ClearStringBuilder(); + }; } /// - /// Releases the object's resources. + /// Returns a string that represents the current object. /// - protected override void OnReleaseResources() - { - ClearStringBuilder(); - base.OnReleaseResources(); - } + /// A string that represents the current object. + public override string ToString() => StringBuilder.ToString(); /// /// Clears the property, using specific methods depending on diff --git a/src/CodeProject.ObjectPool/TimedObjectPool.cs b/src/CodeProject.ObjectPool/TimedObjectPool.cs index 7098b4b..c0087a1 100644 --- a/src/CodeProject.ObjectPool/TimedObjectPool.cs +++ b/src/CodeProject.ObjectPool/TimedObjectPool.cs @@ -23,6 +23,8 @@ #if !(NETSTD10 || NETSTD11) +using CodeProject.ObjectPool.Core; +using PommaLabs.Thrower; using System; using System.Linq; using System.Threading; @@ -39,13 +41,84 @@ namespace CodeProject.ObjectPool internal class TimedObjectPool : ObjectPool, ITimedObjectPool where T : PooledObject { + #region Fields + + /// + /// Backing field for . + /// private TimeSpan _timeout; + + /// + /// The timer which periodically cleans the pool up. + /// private Timer _timer; - public TimedObjectPool() + #endregion Fields + + #region C'tor and Initialization code + + /// + /// Initializes a new timed pool with default settings and specified timeout. + /// + /// The timeout of each pooled object. + /// + /// is less than or equal to . + /// + public TimedObjectPool(TimeSpan timeout) + : this(ObjectPool.DefaultPoolMaximumSize, null, timeout) + { + } + + /// + /// Initializes a new timed pool with specified maximum pool size and timeout. + /// + /// The maximum pool size limit. + /// The timeout of each pooled object. + /// + /// is less than or equal to zero. + /// is less than or equal to . + /// + public TimedObjectPool(int maximumPoolSize, TimeSpan timeout) + : this(maximumPoolSize, null, timeout) { } + /// + /// Initializes a new timed pool with specified factory method and timeout. + /// + /// The factory method that will be used to create new objects. + /// The timeout of each pooled object. + /// + /// is less than or equal to . + /// + public TimedObjectPool(Func factoryMethod, TimeSpan timeout) + : this(ObjectPool.DefaultPoolMaximumSize, factoryMethod, timeout) + { + } + + /// + /// Initializes a new timed pool with specified factory method, maximum size and timeout. + /// + /// The maximum pool size limit. + /// The factory method that will be used to create new objects. + /// The timeout of each pooled object. + /// + /// is less than or equal to zero. + /// is less than or equal to . + /// + public TimedObjectPool(int maximumPoolSize, Func factoryMethod, TimeSpan timeout) : base(maximumPoolSize, factoryMethod) + { + // Preconditions + Raise.ArgumentOutOfRangeException.IfIsLessOrEqual(timeout, TimeSpan.Zero, nameof(timeout), ErrorMessages.NegativeOrZeroTimeout); + + // Assigning properties. + Timeout = timeout; + } + + #endregion C'tor and Initialization code + + #region Public Properties + /// /// When pooled objects have not been used for a time greater than , /// then they will be destroyed by a cleaning task. @@ -60,6 +133,10 @@ public TimeSpan Timeout } } + #endregion Public Properties + + #region Core Methods + /// /// Updates the timer according to a new timeout. /// @@ -92,6 +169,8 @@ private void UpdateTimeout() }, null, _timeout, _timeout); } } + + #endregion Core Methods } } diff --git a/test/CodeProject.ObjectPool.Examples/Program.cs b/test/CodeProject.ObjectPool.Examples/Program.cs index 877730e..3a3f247 100644 --- a/test/CodeProject.ObjectPool.Examples/Program.cs +++ b/test/CodeProject.ObjectPool.Examples/Program.cs @@ -21,6 +21,8 @@ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT // OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +using System; + namespace CodeProject.ObjectPool.Examples { /// @@ -48,8 +50,8 @@ private static void Main() var newPool = new ObjectPool>(() => new PooledObjectWrapper(CreateNewResource()) { - WrapperReleaseResourcesAction = r => ExternalResourceReleaseResource(r), - WrapperResetStateAction = r => ExternalResourceResetState(r) + OnReleaseResources = ExternalResourceReleaseResource, + OnResetState = ExternalResourceResetState }); using (var wrapper = newPool.GetObject()) @@ -57,6 +59,8 @@ private static void Main() // wrapper.InternalResource contains the object that you pooled. wrapper.InternalResource.DoOtherStuff(); } // Exiting the using scope will return the object back to the pool. + + Console.Read(); } private static ExternalExpensiveResource CreateNewResource() @@ -77,19 +81,22 @@ public static void ExternalResourceReleaseResource(ExternalExpensiveResource res internal sealed class ExpensiveResource : PooledObject { - public void DoStuff() + public ExpensiveResource() { - // Do some work here, for example. - } + OnReleaseResources = () => + { + // Called if the resource needs to be manually cleaned before the memory is reclaimed. + }; - protected override void OnReleaseResources() - { - // Override if the resource needs to be manually cleaned before the memory is reclaimed. + OnResetState = () => + { + // Called if the resource needs resetting before it is getting back into the pool. + }; } - protected override void OnResetState() + public void DoStuff() { - // Override if the resource needs resetting before it is getting back into the pool. + // Do some work here, for example. } }