diff --git a/CHANGELOG.md b/CHANGELOG.md index fefb414..69781a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog for CodeProject.ObjectPool # +### v3.0.3 (2017-04-08) ### + +* Added a timed object pool (issue #1). +* OnReleaseResources and OnResetState are now simple actions on PooledObject. + ### v3.0.2 (2017-04-02) ### * Moved core pool buffer into dedicated class: Core.PooledObjectBuffer. diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..a605ae4 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ObjectPool.sln b/ObjectPool.sln index 2fdfbc2..1de97e7 100644 --- a/ObjectPool.sln +++ b/ObjectPool.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.9 +VisualStudioVersion = 15.0.26403.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "root", "root", "{4C2B7C0C-7CDD-4125-B57A-88E168D24190}" ProjectSection(SolutionItems) = preProject @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "root", "root", "{4C2B7C0C-7 build.cake = build.cake CHANGELOG.md = CHANGELOG.md LICENSE.htm = LICENSE.htm + NuGet.Config = NuGet.Config pomma89.snk = pomma89.snk README.md = README.md EndProjectSection diff --git a/README.md b/README.md index ba32bfc..1b5fe1b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A generic, concurrent, portable and flexible Object Pool for the .NET Framework, ## Summary ## -* Latest release version: `v3.0.2` +* Latest release version: `v3.0.3` * Build status on [AppVeyor](https://ci.appveyor.com): [![Build status](https://ci.appveyor.com/api/projects/status/r4qnqaqj9ri6cicn?svg=true)](https://ci.appveyor.com/project/pomma89/objectpool) * [Doxygen](http://www.stack.nl/~dimitri/doxygen/index.html) documentation: + [HTML](https://goo.gl/RVA7mV) @@ -37,9 +37,9 @@ internal static class Program /// private static void Main() { - // Creating a pool with minimum size of 5 and maximum size of 25, using custom Factory - // method to create and instance of ExpensiveResource. - var pool = new ObjectPool(5, 25, () => new ExpensiveResource(/* resource specific initialization */)); + // Creating a pool with a maximum size of 25, using custom Factory method to create and + // instance of ExpensiveResource. + var pool = new ObjectPool(25, () => new ExpensiveResource(/* resource specific initialization */)); using (var resource = pool.GetObject()) { @@ -52,8 +52,8 @@ internal static class Program var newPool = new ObjectPool>(() => new PooledObjectWrapper(CreateNewResource()) { - WrapperReleaseResourcesAction = r => ExternalResourceReleaseResource(r), - WrapperResetStateAction = r => ExternalResourceResetState(r) + OnReleaseResources = ExternalResourceReleaseResource, + OnResetState = ExternalResourceResetState }); using (var wrapper = newPool.GetObject()) @@ -61,6 +61,22 @@ internal static class Program // wrapper.InternalResource contains the object that you pooled. wrapper.InternalResource.DoOtherStuff(); } // Exiting the using scope will return the object back to the pool. + + // Creates a pool where objects which have not been used for over 2 seconds will be + // cleaned up by a dedicated thread. + var timedPool = new TimedObjectPool(TimeSpan.FromSeconds(2)); + + using (var resource = timedPool.GetObject()) + { + // Using the resource... + resource.DoStuff(); + } // Exiting the using scope will return the object back to the pool and record last usage. + + Console.WriteLine($"Timed pool size after 0 seconds: {timedPool.ObjectsInPoolCount}"); // Should be 1 + Thread.Sleep(TimeSpan.FromSeconds(4)); + Console.WriteLine($"Timed pool size after 4 seconds: {timedPool.ObjectsInPoolCount}"); // Should be 0 + + Console.Read(); } private static ExternalExpensiveResource CreateNewResource() @@ -81,19 +97,22 @@ internal static class Program 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. } } @@ -112,91 +131,89 @@ All benchmarks were implemented and run using the wonderful [BenchmarkDotNet](ht ### [Retrieve one object](https://github.com/pomma89/ObjectPool/blob/master/ObjectPool.Benchmarks/RetrieveOneObject.cs) ### -In this benchmark we evaluate how long it takes to extract and return an object stored into the pool, using a single thread. We compare three implementations: +In this benchmark we evaluate how long it takes to extract and return an object stored into the pool, using a single thread. We compare four implementations: * [This project's ObjectPool](https://github.com/pomma89/ObjectPool/blob/master/ObjectPool/ObjectPool.cs) * [This project's ParameterizedObjectPool](https://github.com/pomma89/ObjectPool/blob/master/ObjectPool/ParameterizedObjectPool.cs) * [Microsoft's ObjectPool](http://www.nuget.org/packages/Microsoft.Extensions.ObjectPool/) +* [Original ObjectPool](http://www.codeproject.com/Articles/535735/Implementing-a-Generic-Object-Pool-in-NET) -```ini +``` ini -Host Process Environment Information: -BenchmarkDotNet.Core=v0.9.9.0 -OS=Microsoft Windows NT 6.2.9200.0 -Processor=Intel(R) Core(TM) i3-2330M CPU 2.20GHz, ProcessorCount=4 -Frequency=14318180 ticks, Resolution=69.8413 ns, Timer=HPET -CLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE -GC=Concurrent Workstation -JitModules=clrjit-v4.6.1586.0 +BenchmarkDotNet=v0.10.3.0, OS=Microsoft Windows NT 6.2.9200.0 +Processor=AMD A10 Extreme Edition Radeon R8, 4C+8G, ProcessorCount=4 +Frequency=1949470 Hz, Resolution=512.9599 ns, Timer=TSC + [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0 + RyuJitX64 : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0 -Type=RetrieveOneObject Mode=Throughput +Job=RyuJitX64 Jit=RyuJit Platform=X64 ``` - Method | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op | ------------------------- |------------ |---------- |------ |------ |------ |------------------- | - SimpleObjectPool | 136.0373 ns | 3.4367 ns | 11.02 | 9.58 | 9.58 | 3,09 | - ParameterizedObjectPool | 199.7968 ns | 4.4435 ns | 21.00 | - | - | 3,08 | - MicrosoftObjectPool | 60.7935 ns | 1.8593 ns | - | - | - | 0,00 | + | Method | Mean | StdDev | Gen 0 | Allocated | + |------------------------ |-------------- |----------- |------- |---------- | + | SimpleObjectPool | 106.3367 ns | 1.9033 ns | - | 0 B | + | ParameterizedObjectPool | 174.2507 ns | 1.9017 ns | 0.0391 | 24 B | + | MicrosoftObjectPool | 59.3673 ns | 1.2349 ns | - | 0 B | + | OriginalObjectPool | 1,773.9186 ns | 96.7615 ns | 0.0238 | 240 B | ### [Retrieve objects concurrently](https://github.com/pomma89/ObjectPool/blob/master/ObjectPool.Benchmarks/RetrieveObjectsConcurrently.cs) ### -In this benchmark we evaluate how long it takes to extract and return an object stored into the pool, using `Count` threads. We compare three implementations: +In this benchmark we evaluate how long it takes to extract and return an object stored into the pool, using `Count` threads. We compare four implementations: * [This project's ObjectPool](https://github.com/pomma89/ObjectPool/blob/master/ObjectPool/ObjectPool.cs) * [This project's ParameterizedObjectPool](https://github.com/pomma89/ObjectPool/blob/master/ObjectPool/ParameterizedObjectPool.cs) * [Microsoft's ObjectPool](http://www.nuget.org/packages/Microsoft.Extensions.ObjectPool/) +* [Original ObjectPool](http://www.codeproject.com/Articles/535735/Implementing-a-Generic-Object-Pool-in-NET) -```ini +``` ini -Host Process Environment Information: -BenchmarkDotNet.Core=v0.9.9.0 -OS=Microsoft Windows NT 6.2.9200.0 -Processor=Intel(R) Core(TM) i3-2330M CPU 2.20GHz, ProcessorCount=4 -Frequency=14318180 ticks, Resolution=69.8413 ns, Timer=HPET -CLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE -GC=Concurrent Workstation -JitModules=clrjit-v4.6.1586.0 +BenchmarkDotNet=v0.10.3.0, OS=Microsoft Windows NT 6.2.9200.0 +Processor=AMD A10 Extreme Edition Radeon R8, 4C+8G, ProcessorCount=4 +Frequency=1949470 Hz, Resolution=512.9599 ns, Timer=TSC + [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0 + RyuJitX64 : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0 -Type=RetrieveObjectsConcurrently Mode=Throughput Affinity=2 +Job=RyuJitX64 Jit=RyuJit Platform=X64 ``` - Method | Count | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op | ------------------------- |------ |------------ |---------- |------ |------ |------ |------------------- | - **SimpleObjectPool** | **10** | **5.7390 us** | **0.3109 us** | **1.72** | **0.72** | **0.07** | **278,68** | - ParameterizedObjectPool | 10 | 6.4189 us | 1.1792 us | 1.58 | 1.23 | - | 284,70 | - MicrosoftObjectPool | 10 | 4.2985 us | 0.0975 us | 1.30 | 1.11 | - | 244,84 | - **SimpleObjectPool** | **100** | **19.6048 us** | **1.2525 us** | **3.04** | **2.32** | **0.73** | **576,85** | - ParameterizedObjectPool | 100 | 27.8705 us | 1.9570 us | 3.09 | 2.81 | - | 560,35 | - MicrosoftObjectPool | 100 | 10.5264 us | 1.5110 us | 1.78 | 0.97 | 0.03 | 233,25 | - **SimpleObjectPool** | **1000** | **157.9986 us** | **6.2393 us** | **12.67** | **12.12** | **9.92** | **3.277,64** | - ParameterizedObjectPool | 1000 | 223.4054 us | 7.6698 us | 20.00 | - | - | 2.697,82 | - MicrosoftObjectPool | 1000 | 76.3736 us | 0.8204 us | 5.17 | 0.29 | - | 247,38 | + | Method | Count | Mean | StdDev | Gen 0 | Gen 1 | Allocated | + |------------------------ |------ |-------------- |----------- |-------- |-------- |---------- | + | **SimpleObjectPool** | **10** | **10.1346 us** | **0.3799 us** | **1.7548** | **-** | **1.12 kB** | + | ParameterizedObjectPool | 10 | 13.6740 us | 0.3560 us | 1.9409 | - | 1.42 kB | + | MicrosoftObjectPool | 10 | 9.3135 us | 0.1154 us | 1.7008 | - | 1.12 kB | + | OriginalObjectPool | 10 | 26.4688 us | 0.7511 us | - | - | 3.7 kB | + | **SimpleObjectPool** | **100** | **54.1560 us** | **1.7507 us** | **-** | **-** | **1.31 kB** | + | ParameterizedObjectPool | 100 | 72.3400 us | 0.8960 us | 4.8177 | - | 3.93 kB | + | MicrosoftObjectPool | 100 | 30.9284 us | 1.1752 us | 2.9975 | - | 2.16 kB | + | OriginalObjectPool | 100 | 177.0052 us | 3.7795 us | 1.7904 | - | 27.04 kB | + | **SimpleObjectPool** | **1000** | **689.5726 us** | **21.9610 us** | **-** | **-** | **4.07 kB** | + | ParameterizedObjectPool | 1000 | 844.7284 us | 19.1851 us | 10.9863 | - | 28.18 kB | + | MicrosoftObjectPool | 1000 | 362.2347 us | 21.3030 us | 51.1351 | 15.1438 | 28.3 kB | + | OriginalObjectPool | 1000 | 1,458.5456 us | 25.8381 us | 29.1667 | - | 268.99 kB | + ### [Memory stream pooling](https://github.com/pomma89/ObjectPool/blob/master/ObjectPool.Benchmarks/MemoryStreamPooling.cs) ### -In this benchmark we evaluate how long it takes to extract and return a memory stream stored into the pool, using a single thread. We compare three implementations: +In this benchmark we evaluate how long it takes to extract and return a memory stream stored into the pool, using a single thread. We compare two implementations: * [This project's MemoryStreamPool](https://github.com/pomma89/ObjectPool/blob/master/ObjectPool/Specialized/MemoryStreamPool.cs) * [Microsoft's RecyclableMemoryStreamManager](http://www.nuget.org/packages/Microsoft.IO.RecyclableMemoryStream/) -```ini +``` ini -Host Process Environment Information: -BenchmarkDotNet.Core=v0.9.9.0 -OS=Microsoft Windows NT 6.2.9200.0 -Processor=Intel(R) Core(TM) i3-2330M CPU 2.20GHz, ProcessorCount=4 -Frequency=14318180 ticks, Resolution=69.8413 ns, Timer=HPET -CLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE -GC=Concurrent Workstation -JitModules=clrjit-v4.6.1586.0 +BenchmarkDotNet=v0.10.3.0, OS=Microsoft Windows NT 6.2.9200.0 +Processor=AMD A10 Extreme Edition Radeon R8, 4C+8G, ProcessorCount=4 +Frequency=1949470 Hz, Resolution=512.9599 ns, Timer=TSC + [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0 + RyuJitX64 : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0 -Type=MemoryStreamPooling Mode=Throughput +Job=RyuJitX64 Jit=RyuJit Platform=X64 ``` - Method | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op | ------------------------------- |-------------- |------------ |------- |------ |------ |------------------- | - MemoryStreamPool | 171.7594 ns | 4.7127 ns | 1.46 | 1.26 | 1.26 | 3,08 | - RecyclableMemoryStreamManager | 2,924.0579 ns | 117.2652 ns | 341.00 | - | - | 88,74 | + | Method | Mean | StdErr | StdDev | Gen 0 | Allocated | + |------------------------------ |-------------- |----------- |------------ |------- |---------- | + | MemoryStreamPool | 180.0207 ns | 1.6126 ns | 6.0337 ns | - | 0 B | + | RecyclableMemoryStreamManager | 4,213.5708 ns | 44.6009 ns | 446.0087 ns | 0.8134 | 448 B | ## About this repository and its maintainer ## diff --git a/appveyor.yml b/appveyor.yml index f86f786..5740c9b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,7 @@ #---------------------------------# # version format -version: 3.0.2.{build} +version: 3.0.3.{build} # branches to build branches: @@ -25,7 +25,7 @@ branches: assembly_info: patch: true file: AssemblyInfo.* - assembly_version: "3.0.2.{build}" + assembly_version: "3.0.3.{build}" assembly_file_version: "{version}" assembly_informational_version: "{version}" diff --git a/build.cake b/build.cake index 1d91f0c..c2bee66 100644 --- a/build.cake +++ b/build.cake @@ -92,12 +92,12 @@ private void Build(string cfg) // NoIncremental = true // }); //} - - MSBuild(solutionFile, settings => - { + + MSBuild(solutionFile, settings => + { settings.SetConfiguration(cfg); - settings.SetMaxCpuCount(0); - }); + settings.SetMaxCpuCount(0); + }); } private void Test(string cfg) @@ -107,27 +107,27 @@ private void Test(string cfg) // NoResults = true //}); - const string flags = "--noheader --noresult"; - const string errMsg = " - Unit test failure - "; - - Parallel.ForEach(GetFiles("./test/**/bin/{cfg}/*/*.UnitTests.exe".Replace("{cfg}", cfg)), netExe => - { - if (StartProcess(netExe, flags) != 0) - { - throw new Exception(cfg + errMsg + netExe); - } - }); - - Parallel.ForEach(GetFiles("./test/**/bin/{cfg}/*/*.UnitTests.dll".Replace("{cfg}", cfg)), netCoreDll => - { - DotNetCoreExecute(netCoreDll, flags); - }); + const string flags = "--noheader --noresult"; + const string errMsg = " - Unit test failure - "; + + Parallel.ForEach(GetFiles("./test/**/bin/{cfg}/*/*.UnitTests.exe".Replace("{cfg}", cfg)), netExe => + { + if (StartProcess(netExe, flags) != 0) + { + throw new Exception(cfg + errMsg + netExe); + } + }); + + Parallel.ForEach(GetFiles("./test/**/bin/{cfg}/*/*.UnitTests.dll".Replace("{cfg}", cfg)), netCoreDll => + { + DotNetCoreExecute(netCoreDll, flags); + }); } private void Pack(string cfg) { - Parallel.ForEach(GetFiles("./src/**/*.csproj"), project => - { + Parallel.ForEach(GetFiles("./src/**/*.csproj"), project => + { //DotNetCorePack(project.FullPath, new DotNetCorePackSettings //{ // Configuration = cfg, @@ -135,15 +135,15 @@ private void Pack(string cfg) // NoBuild = true //}); - MSBuild(project, settings => - { - settings.SetConfiguration(cfg); - settings.SetMaxCpuCount(0); - settings.WithTarget("pack"); - settings.WithProperty("IncludeSymbols", new[] { "true" }); - }); - - var packDir = project.GetDirectory().Combine("bin").Combine(cfg); - MoveFiles(GetFiles(packDir + "/*.nupkg"), artifactsDir); - }); + MSBuild(project, settings => + { + settings.SetConfiguration(cfg); + settings.SetMaxCpuCount(0); + settings.WithTarget("pack"); + settings.WithProperty("IncludeSymbols", new[] { "true" }); + }); + + var packDir = project.GetDirectory().Combine("bin").Combine(cfg); + MoveFiles(GetFiles(packDir + "/*.nupkg"), artifactsDir); + }); } \ No newline at end of file diff --git a/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj b/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj index 52d66f9..5d34eb3 100644 --- a/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj +++ b/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj @@ -2,7 +2,7 @@ CodeProject.ObjectPool Generic and concurrent Object Pool - 3.0.2 + 3.0.3 netstandard1.0;netstandard1.1;netstandard1.2;netstandard1.3;net35;net40;net45 true ../../pomma89.snk @@ -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/Core/PooledObjectBuffer.cs b/src/CodeProject.ObjectPool/Core/PooledObjectBuffer.cs index 7c7fb29..d3b726d 100644 --- a/src/CodeProject.ObjectPool/Core/PooledObjectBuffer.cs +++ b/src/CodeProject.ObjectPool/Core/PooledObjectBuffer.cs @@ -41,9 +41,9 @@ public sealed class PooledObjectBuffer : IEnumerable where T : PooledObject { #if (NET35 || NET40) - private const MethodImplOptions TryToInline = default(MethodImplOptions); + private const MethodImplOptions TryInline = default(MethodImplOptions); #else - private const MethodImplOptions TryToInline = MethodImplOptions.AggressiveInlining; + private const MethodImplOptions TryInline = MethodImplOptions.AggressiveInlining; #endif /// @@ -83,9 +83,8 @@ public int Count /// An enumerator that can be used to iterate through the collection. public IEnumerator GetEnumerator() { - for (var i = 0; i <= _pooledObjects.Length; ++i) + foreach (var item in _pooledObjects) { - var item = _pooledObjects[i]; if (item != null) { yield return item; @@ -106,7 +105,7 @@ public IEnumerator GetEnumerator() /// /// Output pooled object. /// True if has a value, false otherwise. - [MethodImpl(TryToInline)] + [MethodImpl(TryInline)] public bool TryDequeue(out T pooledObject) { for (var i = 0; i < _pooledObjects.Length; i++) @@ -127,7 +126,7 @@ public bool TryDequeue(out T pooledObject) /// /// Input pooled object. /// True if there was enough space to enqueue given object, false otherwise. - [MethodImpl(TryToInline)] + [MethodImpl(TryInline)] public bool TryEnqueue(T pooledObject) { for (var i = 0; i < _pooledObjects.Length; i++) @@ -186,5 +185,24 @@ public IList Resize(int newCapacity) Array.Resize(ref _pooledObjects, newCapacity); return exceedingItems; } + + /// + /// Tries to remove given object from the buffer. + /// + /// Pooled object to be removed. + /// True if has been removed, false otherwise. + [MethodImpl(TryInline)] + public bool TryRemove(T pooledObject) + { + for (var i = 0; i < _pooledObjects.Length; i++) + { + var item = _pooledObjects[i]; + if (item != null && item == pooledObject && Interlocked.CompareExchange(ref _pooledObjects[i], null, item) == item) + { + return true; + } + } + return false; + } } } \ No newline at end of file diff --git a/src/CodeProject.ObjectPool/ITimedObjectPool.cs b/src/CodeProject.ObjectPool/ITimedObjectPool.cs index 08605a8..0ac5668 100644 --- a/src/CodeProject.ObjectPool/ITimedObjectPool.cs +++ b/src/CodeProject.ObjectPool/ITimedObjectPool.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 { /// @@ -38,5 +40,10 @@ public interface ITimedObjectPool : IObjectPool #endif where T : PooledObject { + /// + /// When pooled objects have not been used for a time greater than , + /// then they will be destroyed by a cleaning task. + /// + TimeSpan Timeout { get; set; } } } \ No newline at end of file diff --git a/src/CodeProject.ObjectPool/ObjectPool.cs b/src/CodeProject.ObjectPool/ObjectPool.cs index a5b4b64..96b6050 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); + + // 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; - // Assigning properties. - FactoryMethod = factoryMethod; + // 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 /// @@ -245,33 +249,45 @@ void IObjectPoolHandle.ReturnObjectToPool(PooledObject objectToReturnToPool, boo #endregion Pool Operations - #region Private Methods + #region Protected Methods /// /// Keeps track of last pooled object ID. /// private int _lastPooledObjectId; - private T CreatePooledObject() + /// + /// Creates a new pooled object, initializing its info. + /// + /// A new pooled object. + protected virtual T CreatePooledObject() { if (Diagnostics.Enabled) { 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; } - private void DestroyPooledObject(PooledObject objectToDestroy) + /// + /// Destroys given pooled object, disposing its resources. + /// + /// The pooled object that should be destroyed. + protected void DestroyPooledObject(PooledObject objectToDestroy) { // Making sure that the object is only disposed once (in case of application shutting // down and we don't control the order of the finalization). @@ -293,6 +309,6 @@ private void DestroyPooledObject(PooledObject objectToDestroy) GC.SuppressFinalize(objectToDestroy); } - #endregion Private Methods + #endregion Protected Methods } } \ No newline at end of file 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 624ae29..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 - { - OnResetState(); - } - catch (CannotResetStateException crsex) + if (OnResetState != null) { -#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 @@ -199,6 +201,10 @@ private void HandleReAddingToPool(bool reRegisterForFinalization) protected override IEnumerable> GetFormattingMembers() { yield return new KeyValuePair(nameof(PooledObjectInfo.Id), PooledObjectInfo.Id); + if (PooledObjectInfo.Payload != null) + { + yield return new KeyValuePair(nameof(PooledObjectInfo.Payload), PooledObjectInfo.Payload); + } } /// 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 e74fd7b..70b944c 100644 --- a/src/CodeProject.ObjectPool/TimedObjectPool.cs +++ b/src/CodeProject.ObjectPool/TimedObjectPool.cs @@ -23,6 +23,10 @@ #if !(NETSTD10 || NETSTD11) +using CodeProject.ObjectPool.Core; +using PommaLabs.Thrower; +using System; +using System.Linq; using System.Threading; namespace CodeProject.ObjectPool @@ -34,10 +38,156 @@ namespace CodeProject.ObjectPool /// The type of the object that which will be managed by the pool. The pooled object have to be /// a sub-class of PooledObject. /// - internal class TimedObjectPool : ObjectPool, ITimedObjectPool + public class TimedObjectPool : ObjectPool, ITimedObjectPool where T : PooledObject { - private readonly Timer Timer; + #region Fields + + /// + /// Backing field for . + /// + private TimeSpan _timeout; + + /// + /// The timer which periodically cleans the pool up. + /// + private Timer _timer; + + #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. + /// + public TimeSpan Timeout + { + get => _timeout; + set + { + _timeout = value; + UpdateTimeout(); + } + } + + #endregion Public Properties + + #region Core Methods + + /// + /// Creates a new pooled object, initializing its info. + /// + /// A new pooled object. + protected override T CreatePooledObject() + { + var pooledObject = base.CreatePooledObject(); + + // Register an handler which records the time at which the object returned to the pool. + pooledObject.OnResetState += () => + { + pooledObject.PooledObjectInfo.Payload = DateTime.UtcNow; + }; + + return pooledObject; + } + + /// + /// Updates the timer according to a new timeout. + /// + private void UpdateTimeout() + { + lock (this) + { + if (_timer != null) + { + // A timer already exists, simply change its period. + _timer.Change(_timeout, _timeout); + return; + } + + _timer = new Timer(_ => + { + // Local copy, since the buffer might change. + var items = PooledObjects.ToArray(); + + // All items which have been last used before following threshold will be destroyed. + var threshold = DateTime.UtcNow - _timeout; + + foreach (var item in items) + { + if (item.PooledObjectInfo.Payload is DateTime lastUsage && lastUsage < threshold && PooledObjects.TryRemove(item)) + { + DestroyPooledObject(item); + } + } + }, null, _timeout, _timeout); + } + } + + #endregion Core Methods } } diff --git a/src/CodeProject.ObjectPool/doxyfile.txt b/src/CodeProject.ObjectPool/doxyfile.txt index d4a7c9a..4a2c4e3 100644 --- a/src/CodeProject.ObjectPool/doxyfile.txt +++ b/src/CodeProject.ObjectPool/doxyfile.txt @@ -38,7 +38,7 @@ PROJECT_NAME = "Generic and concurrent Object Pool" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 3.0.2 +PROJECT_NUMBER = 3.0.3 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/test/CodeProject.ObjectPool.Examples/Program.cs b/test/CodeProject.ObjectPool.Examples/Program.cs index 877730e..9f50dc5 100644 --- a/test/CodeProject.ObjectPool.Examples/Program.cs +++ b/test/CodeProject.ObjectPool.Examples/Program.cs @@ -21,6 +21,9 @@ // 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; +using System.Threading; + namespace CodeProject.ObjectPool.Examples { /// @@ -48,8 +51,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 +60,22 @@ 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. + + // Creates a pool where objects which have not been used for over 2 seconds will be + // cleaned up by a dedicated thread. + var timedPool = new TimedObjectPool(TimeSpan.FromSeconds(2)); + + using (var resource = timedPool.GetObject()) + { + // Using the resource... + resource.DoStuff(); + } // Exiting the using scope will return the object back to the pool and record last usage. + + Console.WriteLine($"Timed pool size after 0 seconds: {timedPool.ObjectsInPoolCount}"); // Should be 1 + Thread.Sleep(TimeSpan.FromSeconds(4)); + Console.WriteLine($"Timed pool size after 4 seconds: {timedPool.ObjectsInPoolCount}"); // Should be 0 + + Console.Read(); } private static ExternalExpensiveResource CreateNewResource() @@ -77,19 +96,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. } } diff --git a/test/CodeProject.ObjectPool.UnitTests/CodeProject.ObjectPool.UnitTests.csproj b/test/CodeProject.ObjectPool.UnitTests/CodeProject.ObjectPool.UnitTests.csproj index e0b1b67..b1e1762 100644 --- a/test/CodeProject.ObjectPool.UnitTests/CodeProject.ObjectPool.UnitTests.csproj +++ b/test/CodeProject.ObjectPool.UnitTests/CodeProject.ObjectPool.UnitTests.csproj @@ -2,8 +2,7 @@ CodeProject.ObjectPool.UnitTests CodeProject.ObjectPool.UnitTests - - net35;net40;net45;net46 + netcoreapp1.1;net35;net40;net45;net46 1.1.1 Exe false @@ -16,20 +15,21 @@ - - + + + - + - + + $(DefineConstants);NET35