From b30b4abdda15d60443c23442deacac47bcacb4cf Mon Sep 17 00:00:00 2001 From: Ami Bar Date: Sat, 19 Dec 2009 16:27:08 +0200 Subject: [PATCH] v1.0 SmartThreadPool v1.0 --- .gitignore | 54 +- STPExamples/App.ico | Bin 0 -> 1078 bytes STPExamples/AssemblyInfo.cs | 58 + STPExamples/CatchExceptionExample.cs | 47 + STPExamples/GetExceptionExample.cs | 47 + STPExamples/OnWIGIdleEventExample.cs | 38 + STPExamples/PriorityExample.cs | 39 + STPExamples/STPExamples.csproj | 137 ++ STPExamples/SimpleExample.cs | 35 + STPExamples/SuspendedSTPStartExample.cs | 36 + STPExamples/SuspendedWIGStartExample.cs | 39 + STPExamples/WaitForAllExample.cs | 40 + STPExamples/WaitForAnyExample.cs | 43 + STPExamples/WaitForIdleExample.cs | 29 + STPExamples/WorkItemsGroupExample.cs | 34 + STPTests/AssemblyInfo.cs | 58 + STPTests/PermutationGenerator.cs | 225 +++ STPTests/STPTests.csproj | 168 ++ STPTests/TestChainedDelegates.cs | 97 ++ STPTests/TestExceptions.cs | 87 + STPTests/TestGetResult.cs | 198 +++ STPTests/TestMultipleWorkItems.cs | 232 +++ STPTests/TestPostExecute.cs | 227 +++ STPTests/TestPriorityQueue.cs | 169 ++ STPTests/TestStartSuspended.cs | 121 ++ STPTests/TestStateDispose.cs | 144 ++ STPTests/TestThreadPriority.cs | 65 + STPTests/TestWIGChainedDelegates.cs | 101 ++ STPTests/TestWIGConcurrency.cs | 99 ++ STPTests/TestWIGExceptions.cs | 89 ++ STPTests/TestWIGGetResult.cs | 166 ++ STPTests/TestWIGMultipleWorkItems.cs | 227 +++ STPTests/TestWIGPostExecute.cs | 231 +++ STPTests/TestWIGStateDispose.cs | 148 ++ STPTests/TestWIGWaitForIdle.cs | 109 ++ STPTests/TestWaitForIdle.cs | 80 + STPTests/TestWorkItemsGroups.cs | 190 +++ STPTests/TestWorkItemsQueue.cs | 54 + SmartThreadPool.sln | 43 + SmartThreadPool/AssemblyInfo.cs | 61 + SmartThreadPool/CallerThreadContext.cs | 132 ++ SmartThreadPool/Exceptions.cs | 81 + SmartThreadPool/Interfaces.cs | 269 ++++ SmartThreadPool/PriorityQueue.cs | 240 +++ SmartThreadPool/STPPerformanceCounter.cs | 347 ++++ SmartThreadPool/STPStartInfo.cs | 89 ++ SmartThreadPool/SmartThreadPool.cs | 1411 +++++++++++++++++ SmartThreadPool/SmartThreadPool.csproj | 141 ++ SmartThreadPool/WIGStartInfo.cs | 99 ++ SmartThreadPool/WorkItem.cs | 1019 ++++++++++++ SmartThreadPool/WorkItemFactory.cs | 333 ++++ SmartThreadPool/WorkItemInfo.cs | 102 ++ SmartThreadPool/WorkItemsGroup.cs | 512 ++++++ SmartThreadPool/WorkItemsQueue.cs | 600 +++++++ TestSmartThreadPool/App.ico | Bin 0 -> 1078 bytes TestSmartThreadPool/AssemblyInfo.cs | 58 + TestSmartThreadPool/Form1.cs | 786 +++++++++ TestSmartThreadPool/Form1.resx | 485 ++++++ .../TestSmartThreadPool.csproj | 121 ++ UsageControl/AssemblyInfo.cs | 58 + UsageControl/UsageControl.cs | 327 ++++ UsageControl/UsageControl.csproj | 119 ++ UsageControl/UsageControl.resx | 130 ++ UsageControl/UsageHistoryControl.cs | 191 +++ UsageControl/UsageHistoryControl.resx | 130 ++ 65 files changed, 11818 insertions(+), 27 deletions(-) create mode 100644 STPExamples/App.ico create mode 100644 STPExamples/AssemblyInfo.cs create mode 100644 STPExamples/CatchExceptionExample.cs create mode 100644 STPExamples/GetExceptionExample.cs create mode 100644 STPExamples/OnWIGIdleEventExample.cs create mode 100644 STPExamples/PriorityExample.cs create mode 100644 STPExamples/STPExamples.csproj create mode 100644 STPExamples/SimpleExample.cs create mode 100644 STPExamples/SuspendedSTPStartExample.cs create mode 100644 STPExamples/SuspendedWIGStartExample.cs create mode 100644 STPExamples/WaitForAllExample.cs create mode 100644 STPExamples/WaitForAnyExample.cs create mode 100644 STPExamples/WaitForIdleExample.cs create mode 100644 STPExamples/WorkItemsGroupExample.cs create mode 100644 STPTests/AssemblyInfo.cs create mode 100644 STPTests/PermutationGenerator.cs create mode 100644 STPTests/STPTests.csproj create mode 100644 STPTests/TestChainedDelegates.cs create mode 100644 STPTests/TestExceptions.cs create mode 100644 STPTests/TestGetResult.cs create mode 100644 STPTests/TestMultipleWorkItems.cs create mode 100644 STPTests/TestPostExecute.cs create mode 100644 STPTests/TestPriorityQueue.cs create mode 100644 STPTests/TestStartSuspended.cs create mode 100644 STPTests/TestStateDispose.cs create mode 100644 STPTests/TestThreadPriority.cs create mode 100644 STPTests/TestWIGChainedDelegates.cs create mode 100644 STPTests/TestWIGConcurrency.cs create mode 100644 STPTests/TestWIGExceptions.cs create mode 100644 STPTests/TestWIGGetResult.cs create mode 100644 STPTests/TestWIGMultipleWorkItems.cs create mode 100644 STPTests/TestWIGPostExecute.cs create mode 100644 STPTests/TestWIGStateDispose.cs create mode 100644 STPTests/TestWIGWaitForIdle.cs create mode 100644 STPTests/TestWaitForIdle.cs create mode 100644 STPTests/TestWorkItemsGroups.cs create mode 100644 STPTests/TestWorkItemsQueue.cs create mode 100644 SmartThreadPool.sln create mode 100644 SmartThreadPool/AssemblyInfo.cs create mode 100644 SmartThreadPool/CallerThreadContext.cs create mode 100644 SmartThreadPool/Exceptions.cs create mode 100644 SmartThreadPool/Interfaces.cs create mode 100644 SmartThreadPool/PriorityQueue.cs create mode 100644 SmartThreadPool/STPPerformanceCounter.cs create mode 100644 SmartThreadPool/STPStartInfo.cs create mode 100644 SmartThreadPool/SmartThreadPool.cs create mode 100644 SmartThreadPool/SmartThreadPool.csproj create mode 100644 SmartThreadPool/WIGStartInfo.cs create mode 100644 SmartThreadPool/WorkItem.cs create mode 100644 SmartThreadPool/WorkItemFactory.cs create mode 100644 SmartThreadPool/WorkItemInfo.cs create mode 100644 SmartThreadPool/WorkItemsGroup.cs create mode 100644 SmartThreadPool/WorkItemsQueue.cs create mode 100644 TestSmartThreadPool/App.ico create mode 100644 TestSmartThreadPool/AssemblyInfo.cs create mode 100644 TestSmartThreadPool/Form1.cs create mode 100644 TestSmartThreadPool/Form1.resx create mode 100644 TestSmartThreadPool/TestSmartThreadPool.csproj create mode 100644 UsageControl/AssemblyInfo.cs create mode 100644 UsageControl/UsageControl.cs create mode 100644 UsageControl/UsageControl.csproj create mode 100644 UsageControl/UsageControl.resx create mode 100644 UsageControl/UsageHistoryControl.cs create mode 100644 UsageControl/UsageHistoryControl.resx diff --git a/.gitignore b/.gitignore index 7b1e201..f7955ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,28 @@ - -#ignore thumbnails created by windows -Thumbs.db -#Ignore files build by Visual Studio -*.obj -*.exe -*.pdb -*.user -*.aps -*.pch -*.vspscc -*_i.c -*_p.c -*.ncb -*.suo -*.tlb -*.tlh -*.bak -*.cache -*.log -[Bb]in -[Dd]ebug*/ -*.lib -*.sbr -obj/ -[Rr]elease*/ -_ReSharper*/ + +#ignore thumbnails created by windows +Thumbs.db +#Ignore files build by Visual Studio +*.obj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.log +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +obj/ +[Rr]elease*/ +_ReSharper*/ [Tt]est[Rr]esult* \ No newline at end of file diff --git a/STPExamples/App.ico b/STPExamples/App.ico new file mode 100644 index 0000000000000000000000000000000000000000..3a5525fd794f7a7c5c8e6187f470ea3af38cd2b6 GIT binary patch literal 1078 zcmeHHJr05}7=1t!Hp3A*8IHkVf+j?-!eHY14Gtcw1Eb*_9>Bq^zETJ@GKj{_2j4$w zo9}xCh!8{T3=X##Skq>ikMjsvB|y%crWBM2iW(4pI}c%z6%lW!=~4v77#3{z!dmB1 z__&l)-{KUYR+|8|;wB^R|9ET$J@(@=#rd^=)qs85?vAy(PSF5CyNkus435LVkZ$rj zNw|JG-P7^hF<(;#o*Vk}5R#e|^13tBbQkeF?djULtvqyxd3<{9 literal 0 HcmV?d00001 diff --git a/STPExamples/AssemblyInfo.cs b/STPExamples/AssemblyInfo.cs new file mode 100644 index 0000000..9f89a32 --- /dev/null +++ b/STPExamples/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/STPExamples/CatchExceptionExample.cs b/STPExamples/CatchExceptionExample.cs new file mode 100644 index 0000000..19f5019 --- /dev/null +++ b/STPExamples/CatchExceptionExample.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics; +using Amib.Threading; + +namespace Examples +{ + public class CatchExceptionExample + { + private class DivArgs + { + public int x; + public int y; + } + + public void DoWork() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + DivArgs divArgs = new DivArgs(); + divArgs.x = 10; + divArgs.y = 0; + + IWorkItemResult wir = + smartThreadPool.QueueWorkItem(new + WorkItemCallback(this.DoDiv), divArgs); + + try + { + int result = (int)wir.Result; + } + // Catch the exception that Result threw + catch (WorkItemResultException e) + { + // Dump the inner exception which DoDiv threw + Debug.WriteLine(e.InnerException); + } + + smartThreadPool.Shutdown(); + } + + private object DoDiv(object state) + { + DivArgs divArgs = (DivArgs)state; + return (divArgs.x / divArgs.y); + } + } +} diff --git a/STPExamples/GetExceptionExample.cs b/STPExamples/GetExceptionExample.cs new file mode 100644 index 0000000..9a7fe45 --- /dev/null +++ b/STPExamples/GetExceptionExample.cs @@ -0,0 +1,47 @@ +using System; +using Amib.Threading; + +namespace Examples +{ + public class GetExceptionExample + { + private class DivArgs + { + public int x; + public int y; + } + + public void DoWork() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + DivArgs divArgs = new DivArgs(); + divArgs.x = 10; + divArgs.y = 0; + + IWorkItemResult wir = + smartThreadPool.QueueWorkItem(new + WorkItemCallback(this.DoDiv), divArgs); + + Exception e = null; + object obj = wir.GetResult(out e); + // e contains the expetion that DoDiv threw + if(null == e) + { + int result = (int)obj; + } + else + { + // Do something with the exception + } + + smartThreadPool.Shutdown(); + } + + private object DoDiv(object state) + { + DivArgs divArgs = (DivArgs)state; + return (divArgs.x / divArgs.y); + } + } +} diff --git a/STPExamples/OnWIGIdleEventExample.cs b/STPExamples/OnWIGIdleEventExample.cs new file mode 100644 index 0000000..dcd93d6 --- /dev/null +++ b/STPExamples/OnWIGIdleEventExample.cs @@ -0,0 +1,38 @@ +using System.Diagnostics; +using Amib.Threading; + +namespace Examples +{ + public class OnWIGIdleEventExample + { + public void DoWork(object [] states) + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + IWorkItemsGroup wig = smartThreadPool.CreateWorkItemsGroup(1); + + wig.OnIdle += new WorkItemsGroupIdleHandler(wig_OnIdle); + + foreach(object state in states) + { + wig.QueueWorkItem(new + WorkItemCallback(this.DoSomeWork), state); + } + + smartThreadPool.WaitForIdle(); + smartThreadPool.Shutdown(); + } + + private object DoSomeWork(object state) + { + // Do the work + return null; + } + + private void wig_OnIdle(IWorkItemsGroup workItemsGroup) + { + Debug.WriteLine("WIG is idle"); + } + } + +} diff --git a/STPExamples/PriorityExample.cs b/STPExamples/PriorityExample.cs new file mode 100644 index 0000000..d30d8d7 --- /dev/null +++ b/STPExamples/PriorityExample.cs @@ -0,0 +1,39 @@ +using System.Diagnostics; +using Amib.Threading; + +namespace Examples +{ + public class PriorityExample + { + public void DoWork() + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.StartSuspended = true; + + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + "Queued first", + WorkItemPriority.BelowNormal); + + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + "Queued second", + WorkItemPriority.AboveNormal); + + smartThreadPool.Start(); + + smartThreadPool.WaitForIdle(); + + smartThreadPool.Shutdown(); + } + + private object DoSomeWork(object state) + { + Debug.WriteLine(state); + return null; + } + + } +} diff --git a/STPExamples/STPExamples.csproj b/STPExamples/STPExamples.csproj new file mode 100644 index 0000000..3a81549 --- /dev/null +++ b/STPExamples/STPExamples.csproj @@ -0,0 +1,137 @@ + + + Local + 8.0.50727 + 2.0 + {AE943A5A-7CFD-4E0D-BA51-FB763AAEA9A3} + Debug + AnyCPU + App.ico + + + STPExamples + + + JScript + Grid + IE50 + false + Library + STPExamples + OnBuildSuccess + + + + + + + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + System + + + System.Data + + + System.XML + + + SmartThreadPool + {8684FC56-A679-4E2E-8F96-E172FB062EB6} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + \ No newline at end of file diff --git a/STPExamples/SimpleExample.cs b/STPExamples/SimpleExample.cs new file mode 100644 index 0000000..da303ab --- /dev/null +++ b/STPExamples/SimpleExample.cs @@ -0,0 +1,35 @@ +using Amib.Threading; + +namespace Examples +{ + public class SimpleExample + { + public void DoWork(object state) + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + // Queue the work item + IWorkItemResult wir = + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoRealWork), + state); + + // Do some other work here + + // Get the result of the operation + object result = wir.Result; + + smartThreadPool.Shutdown(); + } + + // Do the real work + private object DoRealWork(object state) + { + object result = null; + + // Do the real work here and put the result in 'result' + + return result; + } + } +} diff --git a/STPExamples/SuspendedSTPStartExample.cs b/STPExamples/SuspendedSTPStartExample.cs new file mode 100644 index 0000000..8efd597 --- /dev/null +++ b/STPExamples/SuspendedSTPStartExample.cs @@ -0,0 +1,36 @@ +using System; +using Amib.Threading; + +namespace Examples +{ + public class SuspendedSTPStartExample + { + public void DoWork(object [] states) + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.StartSuspended = true; + + SmartThreadPool smartThreadPool = new SmartThreadPool(stpStartInfo); + + foreach(object state in states) + { + smartThreadPool.QueueWorkItem(new + WorkItemCallback(this.DoSomeWork), state); + } + + // Start working on the work items in the queue + smartThreadPool.Start(); + + // Wait for the completion of all work items + smartThreadPool.WaitForIdle(); + + smartThreadPool.Shutdown(); + } + + private object DoSomeWork(object state) + { + // Do the work + return null; + } + } +} diff --git a/STPExamples/SuspendedWIGStartExample.cs b/STPExamples/SuspendedWIGStartExample.cs new file mode 100644 index 0000000..ccdff07 --- /dev/null +++ b/STPExamples/SuspendedWIGStartExample.cs @@ -0,0 +1,39 @@ +using System; +using Amib.Threading; + +namespace Examples +{ + public class SuspendedWIGStartExample + { + public void DoWork(object [] states) + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + WIGStartInfo wigStartInfo = new WIGStartInfo(); + wigStartInfo.StartSuspended = true; + + IWorkItemsGroup wig = smartThreadPool.CreateWorkItemsGroup(1, wigStartInfo); + + foreach(object state in states) + { + wig.QueueWorkItem(new + WorkItemCallback(this.DoSomeWork), state); + } + + // Start working on the work items in the work items group queue + wig.Start(); + + // Wait for the completion of all work items + wig.WaitForIdle(); + + smartThreadPool.Shutdown(); + } + + private object DoSomeWork(object state) + { + // Do the work + return null; + } + } + +} diff --git a/STPExamples/WaitForAllExample.cs b/STPExamples/WaitForAllExample.cs new file mode 100644 index 0000000..37dd682 --- /dev/null +++ b/STPExamples/WaitForAllExample.cs @@ -0,0 +1,40 @@ +using Amib.Threading; + +namespace Examples +{ + public class WaitForAllExample + { + public void DoWork() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + IWorkItemResult wir1 = + smartThreadPool.QueueWorkItem(new + WorkItemCallback(this.DoSomeWork1), null); + + IWorkItemResult wir2 = + smartThreadPool.QueueWorkItem(new + WorkItemCallback(this.DoSomeWork2), null); + + bool success = SmartThreadPool.WaitAll(new IWorkItemResult [] { wir1, wir2 }); + + if (success) + { + int result1 = (int)wir1.Result; + int result2 = (int)wir2.Result; + } + + smartThreadPool.Shutdown(); + } + + private object DoSomeWork1(object state) + { + return 1; + } + + private object DoSomeWork2(object state) + { + return 2; + } + } +} diff --git a/STPExamples/WaitForAnyExample.cs b/STPExamples/WaitForAnyExample.cs new file mode 100644 index 0000000..ca981bc --- /dev/null +++ b/STPExamples/WaitForAnyExample.cs @@ -0,0 +1,43 @@ +using System.Threading; + +using Amib.Threading; + +namespace Examples +{ + public class WaitForAnyExample + { + public void DoWork() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + IWorkItemResult wir1 = + smartThreadPool.QueueWorkItem(new + WorkItemCallback(this.DoSomeWork1), null); + + IWorkItemResult wir2 = + smartThreadPool.QueueWorkItem(new + WorkItemCallback(this.DoSomeWork2), null); + + IWorkItemResult [] wirs = new IWorkItemResult [] { wir1, wir2 }; + + int index = SmartThreadPool.WaitAny(wirs); + + if (index != WaitHandle.WaitTimeout) + { + int result = (int)wirs[index].Result; + } + + smartThreadPool.Shutdown(); + } + + private object DoSomeWork1(object state) + { + return 1; + } + + private object DoSomeWork2(object state) + { + return 1; + } + } +} diff --git a/STPExamples/WaitForIdleExample.cs b/STPExamples/WaitForIdleExample.cs new file mode 100644 index 0000000..531b6fa --- /dev/null +++ b/STPExamples/WaitForIdleExample.cs @@ -0,0 +1,29 @@ +using Amib.Threading; + +namespace Examples +{ + public class WaitForIdleExample + { + public void DoWork(object [] states) + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + foreach(object state in states) + { + smartThreadPool.QueueWorkItem(new + WorkItemCallback(this.DoSomeWork), state); + } + + // Wait for the completion of all work items + smartThreadPool.WaitForIdle(); + + smartThreadPool.Shutdown(); + } + + private object DoSomeWork(object state) + { + // Do the work + return null; + } + } +} diff --git a/STPExamples/WorkItemsGroupExample.cs b/STPExamples/WorkItemsGroupExample.cs new file mode 100644 index 0000000..43d169f --- /dev/null +++ b/STPExamples/WorkItemsGroupExample.cs @@ -0,0 +1,34 @@ +using System; +using Amib.Threading; + +namespace Examples +{ + public class WorkItemsGroupExample + { + public void DoWork(object [] states) + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + // Create a work items group that processes + // one work item at a time + IWorkItemsGroup wig = smartThreadPool.CreateWorkItemsGroup(1); + + // Queue some work items + foreach(object state in states) + { + wig.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), state); + } + + // Wait for the completion of all work items in the work items group + wig.WaitForIdle(); + + smartThreadPool.Shutdown(); + } + + private object DoSomeWork(object state) + { + // Do the work + return null; + } + } +} diff --git a/STPTests/AssemblyInfo.cs b/STPTests/AssemblyInfo.cs new file mode 100644 index 0000000..9f89a32 --- /dev/null +++ b/STPTests/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/STPTests/PermutationGenerator.cs b/STPTests/PermutationGenerator.cs new file mode 100644 index 0000000..558a25d --- /dev/null +++ b/STPTests/PermutationGenerator.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections; + +using NUnit.Framework; + +using Amib.Threading; +/* + * The code below generates permutations. + * + * The original code was written by Michael Gilleland, + * and can be found in the following site + * http://www.merriampark.com/perm.htm + * + * I translated it to C# from Java. + */ +namespace SmartThreadPoolTests +{ + //-------------------------------------- + // Systematically generate permutations. + //-------------------------------------- + + public class PermutationGenerator : IEnumerable + { + private object [] _objects; + + public PermutationGenerator(object [] objects) + { + _objects = (object [])objects.Clone(); + } + + #region IEnumerable Members + + public IEnumerator GetEnumerator() + { + return new PermutationGeneratorEnumerator(_objects); + } + + #endregion + + private class PermutationGeneratorEnumerator : IEnumerator + { + private object [] _objects; + private object [] _currentPermutation; + private PermutationGeneratorHelper _permutationGeneratorHelper; + + public PermutationGeneratorEnumerator(object [] objects) + { + _objects = objects; + Reset(); + } + + #region IEnumerator Members + + public void Reset() + { + _permutationGeneratorHelper = new PermutationGeneratorHelper(_objects.Length); + } + + public object Current + { + get + { + return _currentPermutation; + } + } + + public bool MoveNext() + { + if (_permutationGeneratorHelper.hasMore()) + { + _currentPermutation = new object[_objects.Length]; + int [] indices = _permutationGeneratorHelper.getNext(); + for (int i = 0; i < indices.Length; i++) + { + _currentPermutation[i] = _objects[indices[i]]; + } + return true; + } + _currentPermutation = null; + return false; + } + + #endregion + } + + + private class PermutationGeneratorHelper + { + + private int[] a; + private long numLeft; + private long total; + + //----------------------------------------------------------- + // Constructor. WARNING: Don't make n too large. + // Recall that the number of permutations is n! + // which can be very large, even when n is as small as 20 -- + // 20! = 2,432,902,008,176,640,000 and + // 21! is too big to fit into a Java long, which is + // why we use long instead. + //---------------------------------------------------------- + + public PermutationGeneratorHelper (int n) + { + if (n < 1) + { + throw new ArgumentOutOfRangeException("n", n, "Min 1"); + } + a = new int[n]; + total = getFactorial (n); + reset(); + } + + //------ + // Reset + //------ + + public void reset () + { + for (int i = 0; i < a.Length; i++) + { + a[i] = i; + } + numLeft = total; + } + + //------------------------------------------------ + // Return number of permutations not yet generated + //------------------------------------------------ + + public long getNumLeft () + { + return numLeft; + } + + //------------------------------------ + // Return total number of permutations + //------------------------------------ + + public long getTotal () + { + return total; + } + + //----------------------------- + // Are there more permutations? + //----------------------------- + + public bool hasMore () + { + return (numLeft > 0); + } + + //------------------ + // Compute factorial + //------------------ + + private static long getFactorial (int n) + { + long fact = 1; + for (int i = n; i > 1; i--) + { + fact = fact * i; + } + return fact; + } + + //-------------------------------------------------------- + // Generate next permutation (algorithm from Rosen p. 284) + //-------------------------------------------------------- + + public int[] getNext () + { + + if (numLeft == total) + { + --numLeft; + return a; + } + + int temp; + + // Find largest index j with a[j] < a[j+1] + + int j = a.Length - 2; + while (a[j] > a[j+1]) + { + j--; + } + + // Find index k such that a[k] is smallest integer + // greater than a[j] to the right of a[j] + + int k = a.Length - 1; + while (a[j] > a[k]) + { + k--; + } + + // Interchange a[j] and a[k] + + temp = a[k]; + a[k] = a[j]; + a[j] = temp; + + // Put tail end of permutation after jth position in increasing order + + int r = a.Length - 1; + int s = j + 1; + + while (r > s) + { + temp = a[s]; + a[s] = a[r]; + a[r] = temp; + r--; + s++; + } + + --numLeft; + return a; + } + } + } +} diff --git a/STPTests/STPTests.csproj b/STPTests/STPTests.csproj new file mode 100644 index 0000000..6942a30 --- /dev/null +++ b/STPTests/STPTests.csproj @@ -0,0 +1,168 @@ + + + Local + 8.0.50727 + 2.0 + {6A3E4DBF-12AD-4636-ACB3-24B5172FAE03} + Debug + AnyCPU + + + + + STPTests + + + JScript + Grid + IE50 + false + Library + STPTests + OnBuildSuccess + + + + + + + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + + System + + + System.Data + + + System.XML + + + SmartThreadPool + {8684FC56-A679-4E2E-8F96-E172FB062EB6} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + \ No newline at end of file diff --git a/STPTests/TestChainedDelegates.cs b/STPTests/TestChainedDelegates.cs new file mode 100644 index 0000000..952b68a --- /dev/null +++ b/STPTests/TestChainedDelegates.cs @@ -0,0 +1,97 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + /// + /// Summary description for TestChainedDelegates. + /// + [TestFixture] + [Category("TestChainedDelegates")] + public class TestChainedDelegates + { + public TestChainedDelegates() + { + } + + [Test] + public void GoodCallback() + { + SmartThreadPool stp = new SmartThreadPool(); + + stp.QueueWorkItem(new WorkItemCallback(DoWork)); + + stp.WaitForIdle(); + + stp.Shutdown(); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void ChainedDelegatesCallback() + { + SmartThreadPool stp = new SmartThreadPool(); + + WorkItemCallback workItemCallback = new WorkItemCallback(DoWork); + workItemCallback += new WorkItemCallback(DoWork); + + stp.QueueWorkItem(workItemCallback); + + stp.WaitForIdle(); + + stp.Shutdown(); + } + + [Test] + public void GoodPostExecute() + { + SmartThreadPool stp = new SmartThreadPool(); + + stp.QueueWorkItem( + new WorkItemCallback(DoWork), + null, + new PostExecuteWorkItemCallback(DoPostExecute)); + + stp.WaitForIdle(); + + stp.Shutdown(); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void ChainedDelegatesPostExecute() + { + SmartThreadPool stp = new SmartThreadPool(); + + PostExecuteWorkItemCallback postExecuteWorkItemCallback = + new PostExecuteWorkItemCallback(DoPostExecute); + postExecuteWorkItemCallback += + new PostExecuteWorkItemCallback(DoPostExecute); + + stp.QueueWorkItem( + new WorkItemCallback(DoWork), + null, + postExecuteWorkItemCallback); + + stp.WaitForIdle(); + + stp.Shutdown(); + } + + + private object DoWork(object state) + { + return null; + } + + private void DoPostExecute(IWorkItemResult wir) + { + } + + + } +} diff --git a/STPTests/TestExceptions.cs b/STPTests/TestExceptions.cs new file mode 100644 index 0000000..4c70c1f --- /dev/null +++ b/STPTests/TestExceptions.cs @@ -0,0 +1,87 @@ +using System; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + /// + /// Summary description for TestExceptions. + /// + [TestFixture] + [Category("TestExceptions")] + public class TestExceptions + { + private class DivArgs + { + public int x; + public int y; + } + + [Test] + public void ExceptionThrowing() + { + SmartThreadPool _smartThreadPool = new SmartThreadPool(); + + DivArgs divArgs = new DivArgs(); + divArgs.x = 10; + divArgs.y = 0; + + IWorkItemResult wir = + _smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoDiv), divArgs); + + try + { + wir.GetResult(); + } + catch(WorkItemResultException wire) + { + Assert.IsTrue(wire.InnerException is DivideByZeroException); + return; + } + catch(Exception e) + { + e.GetHashCode(); + Assert.Fail(); + } + Assert.Fail(); + } + + [Test] + public void ExceptionReturning() + { + bool success = true; + + SmartThreadPool _smartThreadPool = new SmartThreadPool(); + + DivArgs divArgs = new DivArgs(); + divArgs.x = 10; + divArgs.y = 0; + + IWorkItemResult wir = + _smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoDiv), divArgs); + + Exception e = null; + try + { + wir.GetResult(out e); + } + catch (Exception ex) + { + ex.GetHashCode(); + success = false; + } + + Assert.IsTrue(success); + Assert.IsTrue(e is DivideByZeroException); + } + + private object DoDiv(object state) + { + DivArgs divArgs = (DivArgs)state; + return (divArgs.x / divArgs.y); + } + + } +} diff --git a/STPTests/TestGetResult.cs b/STPTests/TestGetResult.cs new file mode 100644 index 0000000..393c02a --- /dev/null +++ b/STPTests/TestGetResult.cs @@ -0,0 +1,198 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + /// + /// Summary description for GetResultExample. + /// + [TestFixture] + [Category("TestGetResult")] + public class TestGetResult + { + /// + /// Example of how to queue a work item and then wait infinitely for the result. + /// + [Test] + public void BlockingCall() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + bool success = false; + + IWorkItemResult wir = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + if (!wir.IsCompleted) + { + int result = (int)wir.GetResult(); + success = (1 == result); + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue a work item and then wait on a timeout for the result. + /// + [Test] + public void Timeout() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + bool success = false; + + IWorkItemResult wir = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + try + { + wir.GetResult(500, true); + } + catch (WorkItemTimeoutException) + { + success = true; + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue a work item and then cancel it while it is in the queue. + /// + [Test] + public void WorkItemCanceling() + { + // Create a SmartThreadPool with only one thread. + // It just to show how to use the work item canceling feature + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 1); + + bool success = false; + + // Queue a work item that will occupy the thread in the pool + IWorkItemResult wir1 = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + // Queue another work item that will wait for the first to complete + IWorkItemResult wir2 = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + // Wait a while for the thread pool to start executing the first work item + Thread.Sleep(100); + + // The first work item cannot be canceled since it is currently executing + if (!wir1.Cancel()) + { + // Cancel the second work item while it still in the queue + if (wir2.Cancel()) + { + try + { + // Retreiving result of a canceled work item throws an exception + wir2.GetResult(); + } + catch (WorkItemCancelException) + { + success = true; + } + } + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to interrupt the waiting for a work item to complete. + /// + [Test] + public void WorkItemWaitCanceling() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + ManualResetEvent cancelWaitHandle = new ManualResetEvent(false); + + bool success = false; + + // Queue a work item that will occupy the thread in the pool + IWorkItemResult wir1 = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + // Queue another work item that will wait for the first to complete + IWorkItemResult wir2 = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.SignalCancel), cancelWaitHandle); + + try + { + wir1.GetResult(System.Threading.Timeout.Infinite, true, cancelWaitHandle); + } + catch (WorkItemTimeoutException) + { + success = true; + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue a work item and then cancel it while it is in the queue. + /// + [Test] + public void WorkItemCancelingAndInUseWorkerThreads() + { + // Create a SmartThreadPool with only one thread. + // It just to show how to use the work item canceling feature + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 10); + + IWorkItemResult [] wirs = new IWorkItemResult[100]; + for(int i = 0; i < 100; ++i) + { + wirs[i] = smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + + // Wait a while for the thread pool to start executing the first work item + Thread.Sleep(100); + + for(int i = 0; i < 100; ++i) + { + wirs[i].Cancel(); + } + + smartThreadPool.WaitForIdle(2000); + + int inUseThreads = smartThreadPool.InUseThreads; + + smartThreadPool.Shutdown(); + + Assert.AreEqual(0, inUseThreads); + } + + + + private object DoSomeWork(object state) + { + Thread.Sleep(1000); + return 1; + } + + private object SignalCancel(object state) + { + ManualResetEvent cancelWaitHandle = state as ManualResetEvent; + Thread.Sleep(250); + cancelWaitHandle.Set(); + return null; + } + } +} diff --git a/STPTests/TestMultipleWorkItems.cs b/STPTests/TestMultipleWorkItems.cs new file mode 100644 index 0000000..ea23375 --- /dev/null +++ b/STPTests/TestMultipleWorkItems.cs @@ -0,0 +1,232 @@ +using System; +using System.Diagnostics; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + /// + /// Summary description for MultipleWorkItemsExample. + /// + [TestFixture] + [Category("TestMultipleWorkItems")] + public class TestMultipleWorkItems + { + /// + /// Example of how to queue several work items and then wait infinitely for + /// all of them to complete. + /// + [Test] + public void WaitAll() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + SmartThreadPool.WaitAll(wirs); + + for(int i = 0; i < wirs.Length; ++i) + { + if (!wirs[i].IsCompleted) + { + success = false; + break; + } + else + { + int result = (int)wirs[i].GetResult(); + if (1 != result) + { + success = false; + break; + } + } + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait infinitely for + /// one of them to complete. + /// + /// You can use this technique if you have several work items that return the same + /// infomration, but use different method to aquire it. Just execute all of them at + /// once and wait for the first work item to complete. + /// + /// For example: You need an information about a person and you can query several + /// information sites (FBI, CIA, etc.). Query all of them at once and use the first + /// answer to arrive. + /// + [Test] + public void WaitAny() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + bool success = false; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + int index = SmartThreadPool.WaitAny(wirs); + + if (wirs[index].IsCompleted) + { + int result = (int)wirs[index].GetResult(); + if (1 == result) + { + success = true; + } + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait on a timeout for all + /// of them to complete. + /// + [Test] + public void WaitAllWithTimeoutSuccess() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + bool timeout = !SmartThreadPool.WaitAll(wirs, 1500, true); + success = !timeout; + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait on a timeout for all + /// of them to complete. + /// + [Test] + public void WaitAllWithTimeoutFailure() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + bool timeout = !SmartThreadPool.WaitAll(wirs, 10, true); + success = timeout; + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait on a timeout for any + /// of them to complete. + /// + [Test] + public void WaitAnyWithTimeoutSuccess() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + int index = SmartThreadPool.WaitAny(wirs, 1500, true); + + success = (index != WaitHandle.WaitTimeout); + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait on a timeout for any + /// of them to complete. + /// + [Test] + public void WaitAnyWithTimeoutFailure() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + smartThreadPool.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + int index = SmartThreadPool.WaitAny(wirs, 10, true); + + success = (index == WaitHandle.WaitTimeout); + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + [Test] + public void WaitAllWithEmptyArray() + { + + IWorkItemResult [] wirs = new IWorkItemResult[0]; + + bool success = SmartThreadPool.WaitAll(wirs);; + + Assert.IsTrue(success); + } + + private object DoSomeWork(object state) + { + Thread.Sleep(1000); + return 1; + } + } +} diff --git a/STPTests/TestPostExecute.cs b/STPTests/TestPostExecute.cs new file mode 100644 index 0000000..dbee9bf --- /dev/null +++ b/STPTests/TestPostExecute.cs @@ -0,0 +1,227 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + /// + /// Summary description for DoTestPostExecute. + /// + [TestFixture] + [Category("DoTestPostExecute")] + public class TestPostExecution + { + /// + /// + /// + [Test] + public void DefaultPostExecute_AlwaysCall() + { + Assert.IsTrue(DoTestDefaultPostExecute(CallToPostExecute.Always, true)); + } + + [Test] + public void DefaultPostExecute_NeverCall() + { + Assert.IsTrue(DoTestDefaultPostExecute(CallToPostExecute.Never, false)); + } + + [Test] + public void DefaultPostExecute_CallWhenCanceled() + { + Assert.IsTrue(DoTestDefaultPostExecute(CallToPostExecute.WhenWorkItemCanceled, false)); + } + + [Test] + public void DefaultPostExecute_CallWhenNotCanceled() + { + Assert.IsTrue(DoTestDefaultPostExecute(CallToPostExecute.WhenWorkItemNotCanceled, true)); + } + + /// + /// + /// + [Test] + public void PostExecute_AlwaysCall() + { + Assert.IsTrue(DoTestPostExecute(CallToPostExecute.Always, true)); + } + + [Test] + public void PostExecute_NeverCall() + { + Assert.IsTrue(DoTestPostExecute(CallToPostExecute.Never, false)); + } + + [Test] + public void PostExecute_CallWhenCanceled() + { + Assert.IsTrue(DoTestPostExecute(CallToPostExecute.WhenWorkItemCanceled, false)); + } + + [Test] + public void PostExecute_CallWhenNotCanceled() + { + Assert.IsTrue(DoTestPostExecute(CallToPostExecute.WhenWorkItemNotCanceled, true)); + } + + /// + /// + /// + [Test] + public void PostExecuteWithCancel_AlwaysCall() + { + Assert.IsTrue(DoTestPostExecuteWithCancel(CallToPostExecute.Always, true)); + } + + [Test] + public void PostExecuteWithCancel_NeverCall() + { + Assert.IsTrue(DoTestPostExecuteWithCancel(CallToPostExecute.Never, false)); + } + + [Test] + public void PostExecuteWithCancel_CallWhenCanceled() + { + Assert.IsTrue(DoTestPostExecuteWithCancel(CallToPostExecute.WhenWorkItemCanceled, true)); + } + + [Test] + public void PostExecuteWithCancel_CallWhenNotCanceled() + { + Assert.IsTrue(DoTestPostExecuteWithCancel(CallToPostExecute.WhenWorkItemNotCanceled, false)); + } + + + private class PostExecuteResult + { + public ManualResetEvent wh = new ManualResetEvent(false); + } + + /// + /// Example of how to use the post execute callback + /// + private bool DoTestDefaultPostExecute(CallToPostExecute callToPostExecute, bool answer) + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.CallToPostExecute = callToPostExecute; + stpStartInfo.PostExecuteWorkItemCallback = new PostExecuteWorkItemCallback(this.DoSomePostExecuteWork); + + SmartThreadPool smartThreadPool = new SmartThreadPool(stpStartInfo); + + bool success = false; + + PostExecuteResult postExecuteResult = new PostExecuteResult(); + + IWorkItemResult wir = + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + postExecuteResult); + + if (!wir.IsCompleted) + { + int result = (int)wir.GetResult(); + success = (1 == result); + success = success && (postExecuteResult.wh.WaitOne(1000, true) == answer); + } + + smartThreadPool.Shutdown(); + + return success; + } + + + /// + /// Example of how to use the post execute callback + /// + private bool DoTestPostExecute(CallToPostExecute callToPostExecute, bool answer) + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + bool success = false; + + PostExecuteResult postExecuteResult = new PostExecuteResult(); + + IWorkItemResult wir = + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + postExecuteResult, + new PostExecuteWorkItemCallback(this.DoSomePostExecuteWork), + callToPostExecute); + + if (!wir.IsCompleted) + { + int result = (int)wir.GetResult(); + success = (1 == result); + success = success && (postExecuteResult.wh.WaitOne(1000, true) == answer); + } + + smartThreadPool.Shutdown(); + + return success; + } + + /// + /// Example of how to queue a work item and then cancel it while it is in the queue. + /// + private bool DoTestPostExecuteWithCancel(CallToPostExecute callToPostExecute, bool answer) + { + // Create a SmartThreadPool with only one thread. + // It just to show how to use the work item canceling feature + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 1); + + bool success = false; + PostExecuteResult postExecuteResult = new PostExecuteResult(); + + // Queue a work item that will occupy the thread in the pool + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + null); + + // Queue another work item that will wait for the first to complete + IWorkItemResult wir = + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + postExecuteResult, + new PostExecuteWorkItemCallback(this.DoSomePostExecuteWork), + callToPostExecute); + + + // Wait a while for the thread pool to start executing the first work item + Thread.Sleep(100); + + // Cancel the second work item while it still in the queue + if (wir.Cancel()) + { + success = (postExecuteResult.wh.WaitOne(1000, true) == answer); + } + + smartThreadPool.Shutdown(); + + return success; + } + + private object DoSomeWork(object state) + { + Thread.Sleep(1000); + return 1; + } + + private void DoSomePostExecuteWork(IWorkItemResult wir) + { + PostExecuteResult postExecuteResult = wir.State as PostExecuteResult; + postExecuteResult.wh.Set(); + } + + private object SignalCancel(object state) + { + ManualResetEvent cancelWaitHandle = state as ManualResetEvent; + Thread.Sleep(250); + cancelWaitHandle.Set(); + return null; + } + } +} diff --git a/STPTests/TestPriorityQueue.cs b/STPTests/TestPriorityQueue.cs new file mode 100644 index 0000000..d0180fc --- /dev/null +++ b/STPTests/TestPriorityQueue.cs @@ -0,0 +1,169 @@ +using System; + +using NUnit.Framework; + +using Amib.Threading; +using Amib.Threading.Internal; +using SmartThreadPoolTests; + +namespace PriorityQueueTests +{ + /// + /// Summary description for TestPriorityQueue. + /// + [TestFixture] + [Category("TestPriorityQueue")] + public class TestPriorityQueue + { + public TestPriorityQueue() + { + } + + [Test] + public void Init() + { + PriorityQueue pq = new PriorityQueue(); + + Assert.AreEqual(0, pq.Count); + + Assert.IsNull(pq.Dequeue()); + + Assert.AreEqual(0, pq.Count); + } + + [Test] + public void OneWorkItem() + { + WorkItemPriority [] priorities = Enum.GetValues(typeof(WorkItemPriority)) as WorkItemPriority []; + foreach(WorkItemPriority wip in priorities) + { + PriorityQueue pq = new PriorityQueue(); + + PriorityItem pi = new PriorityItem(wip); + + pq.Enqueue(pi); + + Assert.AreEqual(1, pq.Count, "Failed for priority {0}", wip); + + PriorityItem pi2 = pq.Dequeue() as PriorityItem; + + Assert.IsNotNull(pi2, "Failed for priority {0}", wip); + + Assert.AreSame(pi, pi2, "Failed for priority {0}", wip); + + Assert.AreEqual(0, pq.Count, "Failed for priority {0}", wip); + } + } + + [Test] + public void MultipleWorkItemsOnePriority() + { + WorkItemPriority [] priorities = Enum.GetValues(typeof(WorkItemPriority)) as WorkItemPriority []; + foreach(WorkItemPriority wip in priorities) + { + PriorityQueue pq = new PriorityQueue(); + + PriorityItem [] priorityItems = new PriorityItem[10]; + + for(int i = 0; i < priorityItems.Length; ++i) + { + priorityItems[i] = new PriorityItem(wip); + + pq.Enqueue(priorityItems[i]); + + Assert.AreEqual(i+1, pq.Count, "Failed for priority {0} item count {1}", wip, i+1); + } + + for(int i = 0; i < priorityItems.Length; ++i) + { + PriorityItem pi = pq.Dequeue() as PriorityItem; + + Assert.AreEqual(priorityItems.Length-(i+1), pq.Count, "Failed for priority {0} item count {1}", wip, i+1); + + Assert.IsNotNull(pi, "Failed for priority {0} item count {1}", wip, i+1); + + Assert.AreSame(pi, priorityItems[i], "Failed for priority {0} item count {1}", wip, i+1); + } + + Assert.AreEqual(0, pq.Count, "Failed for priority {0}", wip); + + Assert.IsNull(pq.Dequeue()); + + Assert.AreEqual(0, pq.Count); + } + } + + [Test] + public void MultipleWorkItemsMultiplePriorities() + { + // Get all the available priorities + WorkItemPriority [] priorities = Enum.GetValues(typeof(WorkItemPriority)) as WorkItemPriority []; + + // Create an array of priority items + PriorityItem [] priorityItems = new PriorityItem[priorities.Length]; + + // Create a priority item for each priority + int i = priorities.Length; + foreach(WorkItemPriority workItemPriority in priorities) + { + --i; + priorityItems[i] = new PriorityItem(workItemPriority); + } + + // Create a PermutationGenerator for the priority items + PermutationGenerator permutations = new PermutationGenerator(priorityItems); + + int count = 0; + // Iterate over the permutations + foreach(object [] permutation in permutations) + { + ++count; + Console.Write("Permutation #" + count + " : "); + for(int j = 0; j < permutation.Length; ++j) + { + PriorityItem pi = permutation[j] as PriorityItem; + Console.Write(pi.WorkItemPriority + ", "); + } + Console.WriteLine(); + // Create a priority queue + PriorityQueue pq = new PriorityQueue(); + + // Enqueue each priority item according to the permutation + for(i = 0; i < permutation.Length; ++i) + { + PriorityItem priorityItem = permutation[i] as PriorityItem; + pq.Enqueue(priorityItem); + } + + // Make sure all the priority items are in the queue + Assert.AreEqual(priorityItems.Length, pq.Count); + + // Compare the order of the priority items + for(i = 0; i < priorityItems.Length; ++i) + { + PriorityItem priorityItem = pq.Dequeue() as PriorityItem; + Assert.AreSame(priorityItems[i], priorityItem); + } + } + } + + private class PriorityItem : IHasWorkItemPriority + { + private WorkItemPriority _workItemPriority; + + public PriorityItem(WorkItemPriority workItemPriority) + { + _workItemPriority = workItemPriority; + } + + public WorkItemPriority WorkItemPriority + { + get + { + return _workItemPriority; + } + } + } + + } +} diff --git a/STPTests/TestStartSuspended.cs b/STPTests/TestStartSuspended.cs new file mode 100644 index 0000000..fc2481a --- /dev/null +++ b/STPTests/TestStartSuspended.cs @@ -0,0 +1,121 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + /// + /// Summary description for TestStartSuspended. + /// + [TestFixture] + [Category("TestStartSuspended")] + public class TestStartSuspended + { + public TestStartSuspended() + { + } + + [Test] + public void StartSuspended() + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.StartSuspended = true; + + SmartThreadPool stp = new SmartThreadPool(stpStartInfo); + + stp.QueueWorkItem(new WorkItemCallback(this.DoWork)); + + Assert.IsFalse(stp.WaitForIdle(200)); + + stp.Start(); + + Assert.IsTrue(stp.WaitForIdle(200)); + } + + [Test] + public void WIGStartSuspended() + { + SmartThreadPool stp = new SmartThreadPool(); + + WIGStartInfo wigStartInfo = new WIGStartInfo(); + wigStartInfo.StartSuspended = true; + + IWorkItemsGroup wig = stp.CreateWorkItemsGroup(10, wigStartInfo); + + wig.QueueWorkItem(new WorkItemCallback(this.DoWork)); + + Assert.IsFalse(wig.WaitForIdle(200)); + + wig.Start(); + + Assert.IsTrue(wig.WaitForIdle(200)); + } + + [Test] + public void STPAndWIGStartSuspended() + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.StartSuspended = true; + + SmartThreadPool stp = new SmartThreadPool(stpStartInfo); + + WIGStartInfo wigStartInfo = new WIGStartInfo(); + wigStartInfo.StartSuspended = true; + + IWorkItemsGroup wig = stp.CreateWorkItemsGroup(10, wigStartInfo); + + wig.QueueWorkItem(new WorkItemCallback(this.DoWork)); + + Assert.IsFalse(wig.WaitForIdle(200)); + + wig.Start(); + + Assert.IsFalse(wig.WaitForIdle(200)); + + stp.Start(); + + Assert.IsTrue(wig.WaitForIdle(200)); + Assert.IsTrue(stp.WaitForIdle(0)); + } + + + [Test] + public void TwoWIGsStartSuspended() + { + SmartThreadPool stp = new SmartThreadPool(); + + WIGStartInfo wigStartInfo = new WIGStartInfo(); + wigStartInfo.StartSuspended = true; + + IWorkItemsGroup wig1 = stp.CreateWorkItemsGroup(10, wigStartInfo); + IWorkItemsGroup wig2 = stp.CreateWorkItemsGroup(10, wigStartInfo); + + wig1.QueueWorkItem(new WorkItemCallback(this.DoWork)); + wig2.QueueWorkItem(new WorkItemCallback(this.DoWork)); + + Assert.IsFalse(wig1.WaitForIdle(200)); + Assert.IsFalse(wig2.WaitForIdle(200)); + + wig1.Start(); + + Assert.IsTrue(wig1.WaitForIdle(200)); + Assert.IsFalse(wig2.WaitForIdle(200)); + + wig2.Start(); + + Assert.IsTrue(wig1.WaitForIdle(0)); + Assert.IsTrue(wig2.WaitForIdle(200)); + } + + + private object DoWork(object state) + { + Thread.Sleep(100); + return null; + } + + } +} diff --git a/STPTests/TestStateDispose.cs b/STPTests/TestStateDispose.cs new file mode 100644 index 0000000..0884930 --- /dev/null +++ b/STPTests/TestStateDispose.cs @@ -0,0 +1,144 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + public class CallerState + { + private int _val = 0; + + public int Value + { + get { return _val; } + } + + protected void IncValue() + { + ++_val; + } + } + + public class NonDisposableCallerState : CallerState + { + public NonDisposableCallerState() + { + IncValue(); + } + } + + public class DisposableCallerState : CallerState, IDisposable + { + public DisposableCallerState() + { + IncValue(); + } + + #region IDisposable Members + + public void Dispose() + { + IncValue(); + } + + #endregion + } + + + /// + /// Summary description for StateDisposeExample. + /// + [TestFixture] + [Category("TestStateDispose")] + public class TestStateDispose + { + public TestStateDispose() + { + } + + /// + /// Example of non disposable caller state + /// + [Test] + public void DisposeCallerState() + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.DisposeOfStateObjects = true; + + SmartThreadPool smartThreadPool = new SmartThreadPool(stpStartInfo); + + CallerState nonDisposableCallerState = new NonDisposableCallerState(); + CallerState disposableCallerState = new DisposableCallerState(); + + IWorkItemResult wir1 = + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + nonDisposableCallerState); + + IWorkItemResult wir2 = + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + disposableCallerState); + + wir1.GetResult(); + Assert.AreEqual(1, nonDisposableCallerState.Value); + + wir2.GetResult(); + + // Wait a little bit for the working thread to call dispose on the + // work item's state. + smartThreadPool.WaitForIdle(); + + Assert.AreEqual(2, disposableCallerState.Value); + + smartThreadPool.Shutdown(); + } + + /// + /// Example of non disposable caller state + /// + [Test] + public void DontDisposeCallerState() + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.DisposeOfStateObjects = false; + + SmartThreadPool smartThreadPool = new SmartThreadPool(stpStartInfo); + + bool success = false; + + CallerState nonDisposableCallerState = new NonDisposableCallerState(); + CallerState disposableCallerState = new DisposableCallerState(); + + IWorkItemResult wir1 = + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + nonDisposableCallerState); + + IWorkItemResult wir2 = + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + disposableCallerState); + + wir1.GetResult(); + success = (1 == nonDisposableCallerState.Value); + + wir2.GetResult(); + + success = success && (1 == disposableCallerState.Value); + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + private object DoSomeWork(object state) + { + Thread.Sleep(1000); + return 1; + } + } +} diff --git a/STPTests/TestThreadPriority.cs b/STPTests/TestThreadPriority.cs new file mode 100644 index 0000000..a624dbc --- /dev/null +++ b/STPTests/TestThreadPriority.cs @@ -0,0 +1,65 @@ + +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + /// + /// Summary description for TestThreadPriority. + /// + [TestFixture] + [Category("TestThreadPriority")] + public class TestThreadPriority + { + [Test] + public void TestDefaultPriority() + { + SmartThreadPool stp = new SmartThreadPool(); + + IWorkItemResult wir = stp.QueueWorkItem(new WorkItemCallback(DoSomeWork)); + ThreadPriority currentThreadPriority = (ThreadPriority)wir.GetResult(); + + Assert.AreEqual(currentThreadPriority, SmartThreadPool.DefaultThreadPriority); + } + + [Test] + public void TestPriorities() + { + ThreadPriority [] priorities = + { + ThreadPriority.Lowest, + ThreadPriority.BelowNormal, + ThreadPriority.Normal, + ThreadPriority.AboveNormal, + ThreadPriority.Highest, + }; + + foreach(ThreadPriority priority in priorities) + { + CheckSinglePriority(priority); + } + } + + private void CheckSinglePriority(ThreadPriority threadPriority) + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.ThreadPriority = threadPriority; + + SmartThreadPool stp = new SmartThreadPool(stpStartInfo); + + IWorkItemResult wir = stp.QueueWorkItem(new WorkItemCallback(DoSomeWork)); + ThreadPriority currentThreadPriority = (ThreadPriority)wir.GetResult(); + + Assert.AreEqual(currentThreadPriority, threadPriority); + } + + private object DoSomeWork(object state) + { + return Thread.CurrentThread.Priority; + } + } +} \ No newline at end of file diff --git a/STPTests/TestWIGChainedDelegates.cs b/STPTests/TestWIGChainedDelegates.cs new file mode 100644 index 0000000..fce93b9 --- /dev/null +++ b/STPTests/TestWIGChainedDelegates.cs @@ -0,0 +1,101 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace WorkItemsGroupTests +{ + /// + /// Summary description for TestChainedDelegates. + /// + [TestFixture] + [Category("Test WorkItemsGroup ChainedDelegates")] + public class TestChainedDelegates + { + public TestChainedDelegates() + { + } + + [Test] + public void GoodCallback() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + workItemsGroup.QueueWorkItem(new WorkItemCallback(DoWork)); + + workItemsGroup.WaitForIdle(); + + smartThreadPool.Shutdown(); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void ChainedDelegatesCallback() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + WorkItemCallback workItemCallback = new WorkItemCallback(DoWork); + workItemCallback += new WorkItemCallback(DoWork); + + workItemsGroup.QueueWorkItem(workItemCallback); + + workItemsGroup.WaitForIdle(); + + smartThreadPool.Shutdown(); + } + + [Test] + public void GoodPostExecute() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + workItemsGroup.QueueWorkItem( + new WorkItemCallback(DoWork), + null, + new PostExecuteWorkItemCallback(DoPostExecute)); + + workItemsGroup.WaitForIdle(); + + smartThreadPool.Shutdown(); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void ChainedDelegatesPostExecute() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + PostExecuteWorkItemCallback postExecuteWorkItemCallback = + new PostExecuteWorkItemCallback(DoPostExecute); + postExecuteWorkItemCallback += + new PostExecuteWorkItemCallback(DoPostExecute); + + workItemsGroup.QueueWorkItem( + new WorkItemCallback(DoWork), + null, + postExecuteWorkItemCallback); + + workItemsGroup.WaitForIdle(); + + smartThreadPool.Shutdown(); + } + + + private object DoWork(object state) + { + return null; + } + + private void DoPostExecute(IWorkItemResult wir) + { + } + + + } +} diff --git a/STPTests/TestWIGConcurrency.cs b/STPTests/TestWIGConcurrency.cs new file mode 100644 index 0000000..1cf5b5b --- /dev/null +++ b/STPTests/TestWIGConcurrency.cs @@ -0,0 +1,99 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + /// + /// Summary description for TestWIGConcurrency. + /// + [TestFixture] + [Category("TestWIGConcurrency")] + public class TestWIGConcurrency + { + private Random _randGen; + private int [] _concurrentOps; + private int _concurrencyPerWig; + private bool _success; + + public TestWIGConcurrency() + { + } + + [Test] + public void TestConcurrencies() + { + Concurrency(1, 1, 10); + Concurrency(1, 1, 100); + + Concurrency(1, 5, 10); + Concurrency(1, 5, 100); + + Concurrency(5, 5, 10); + Concurrency(5, 5, 100); + } + + private void Concurrency( + int concurrencyPerWig, + int wigsCount, + int workItemsCount) + { + Console.WriteLine( + "Testing : concurrencyPerWig = {0}, wigsCount = {1}, workItemsCount = {2}", + concurrencyPerWig, + wigsCount, + workItemsCount); + + _success = true; + _concurrencyPerWig = concurrencyPerWig; + _randGen = new Random(0); + + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.StartSuspended = true; + + SmartThreadPool stp = new SmartThreadPool(stpStartInfo); + + _concurrentOps = new int[wigsCount]; + + IWorkItemsGroup [] wigs = new IWorkItemsGroup[wigsCount]; + + for(int i = 0; i < wigs.Length; ++i) + { + wigs[i] = stp.CreateWorkItemsGroup(_concurrencyPerWig); + for(int j = 0; j < workItemsCount; ++j) + { + wigs[i].QueueWorkItem(new WorkItemCallback(this.DoWork), i); + } + + wigs[i].Start(); + } + + stp.Start(); + + stp.WaitForIdle(); + + Assert.IsTrue(_success); + + stp.Shutdown(); + } + + private object DoWork(object state) + { + int wigsIndex = (int)state; + + int val = Interlocked.Increment(ref _concurrentOps[wigsIndex]); + _success = _success && (val <= _concurrencyPerWig); + + int waitTime = _randGen.Next(50); + Thread.Sleep(waitTime); + + val = Interlocked.Decrement(ref _concurrentOps[wigsIndex]); + _success = _success && (val >= 0); + + return null; + } + } +} diff --git a/STPTests/TestWIGExceptions.cs b/STPTests/TestWIGExceptions.cs new file mode 100644 index 0000000..0c8a267 --- /dev/null +++ b/STPTests/TestWIGExceptions.cs @@ -0,0 +1,89 @@ +using System; + +using NUnit.Framework; + +using Amib.Threading; + +namespace WorkItemsGroupTests +{ + /// + /// Summary description for TestExceptions. + /// + [TestFixture] + [Category("Test WorkItemsGroup Exceptions")] + public class TestExceptions + { + private class DivArgs + { + public int x; + public int y; + } + + [Test] + public void ExceptionThrowing() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + DivArgs divArgs = new DivArgs(); + divArgs.x = 10; + divArgs.y = 0; + + IWorkItemResult wir = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoDiv), divArgs); + + try + { + wir.GetResult(); + } + catch(WorkItemResultException wire) + { + Assert.IsTrue(wire.InnerException is DivideByZeroException); + return; + } + catch(Exception e) + { + e.GetHashCode(); + Assert.Fail(); + } + Assert.Fail(); + } + + [Test] + public void ExceptionReturning() + { + bool success = true; + + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + DivArgs divArgs = new DivArgs(); + divArgs.x = 10; + divArgs.y = 0; + + IWorkItemResult wir = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoDiv), divArgs); + + Exception e = null; + try + { + wir.GetResult(out e); + } + catch (Exception ex) + { + ex.GetHashCode(); + success = false; + } + + Assert.IsTrue(success); + Assert.IsTrue(e is DivideByZeroException); + } + + private object DoDiv(object state) + { + DivArgs divArgs = (DivArgs)state; + return (divArgs.x / divArgs.y); + } + + } +} diff --git a/STPTests/TestWIGGetResult.cs b/STPTests/TestWIGGetResult.cs new file mode 100644 index 0000000..852a060 --- /dev/null +++ b/STPTests/TestWIGGetResult.cs @@ -0,0 +1,166 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace WorkItemsGroupTests +{ + /// + /// Summary description for GetResultExample. + /// + [TestFixture] + [Category("Test WorkItemsGroup GetResult")] + public class TestGetResult + { + /// + /// Example of how to queue a work item and then wait infinitely for the result. + /// + [Test] + public void BlockingCall() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = false; + + IWorkItemResult wir = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + if (!wir.IsCompleted) + { + int result = (int)wir.GetResult(); + success = (1 == result); + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue a work item and then wait on a timeout for the result. + /// + [Test] + public void Timeout() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = false; + + IWorkItemResult wir = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + try + { + wir.GetResult(500, true); + } + catch (WorkItemTimeoutException) + { + success = true; + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue a work item and then cancel it while it is in the queue. + /// + [Test] + public void WorkItemCanceling() + { + // Create a SmartThreadPool with only one thread. + // It just to show how to use the work item canceling feature + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 1); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = false; + + // Queue a work item that will occupy the thread in the pool + IWorkItemResult wir1 = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + // Queue another work item that will wait for the first to complete + IWorkItemResult wir2 = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + // Wait a while for the thread pool to start executing the first work item + Thread.Sleep(100); + + // The first work item cannot be canceled since it is currently executing + if (!wir1.Cancel()) + { + // Cancel the second work item while it still in the queue + if (wir2.Cancel()) + { + try + { + // Retreiving result of a canceled work item throws an exception + wir2.GetResult(); + } + catch (WorkItemCancelException) + { + success = true; + } + } + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to interrupt the waiting for a work item to complete. + /// + [Test] + public void WorkItemWaitCanceling() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + ManualResetEvent cancelWaitHandle = new ManualResetEvent(false); + + bool success = false; + + // Queue a work item that will occupy the thread in the pool + IWorkItemResult wir1 = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + // Queue another work item that will wait for the first to complete + IWorkItemResult wir2 = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.SignalCancel), cancelWaitHandle); + + try + { + wir1.GetResult(System.Threading.Timeout.Infinite, true, cancelWaitHandle); + } + catch (WorkItemTimeoutException) + { + success = true; + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + private object DoSomeWork(object state) + { + Thread.Sleep(1000); + return 1; + } + + private object SignalCancel(object state) + { + ManualResetEvent cancelWaitHandle = state as ManualResetEvent; + Thread.Sleep(250); + cancelWaitHandle.Set(); + return null; + } + } +} diff --git a/STPTests/TestWIGMultipleWorkItems.cs b/STPTests/TestWIGMultipleWorkItems.cs new file mode 100644 index 0000000..75d0d24 --- /dev/null +++ b/STPTests/TestWIGMultipleWorkItems.cs @@ -0,0 +1,227 @@ +using System; +using System.Diagnostics; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace WorkItemsGroupTests +{ + /// + /// Summary description for MultipleWorkItemsExample. + /// + [TestFixture] + [Category("Test WorkItemsGroup MultipleWorkItems")] + public class TestMultipleWorkItems + { + /// + /// Example of how to queue several work items and then wait infinitely for + /// all of them to complete. + /// + [Test] + public void WaitAll() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + SmartThreadPool.WaitAll(wirs); + + for(int i = 0; i < wirs.Length; ++i) + { + if (!wirs[i].IsCompleted) + { + success = false; + break; + } + else + { + int result = (int)wirs[i].GetResult(); + if (1 != result) + { + success = false; + break; + } + } + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait infinitely for + /// one of them to complete. + /// + /// You can use this technique if you have several work items that return the same + /// infomration, but use different method to aquire it. Just execute all of them at + /// once and wait for the first work item to complete. + /// + /// For example: You need an information about a person and you can query several + /// information sites (FBI, CIA, etc.). Query all of them at once and use the first + /// answer to arrive. + /// + [Test] + public void WaitAny() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = false; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + int index = SmartThreadPool.WaitAny(wirs); + + if (wirs[index].IsCompleted) + { + int result = (int)wirs[index].GetResult(); + if (1 == result) + { + success = true; + } + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait on a timeout for all + /// of them to complete. + /// + [Test] + public void WaitAllWithTimeoutSuccess() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + bool timeout = !SmartThreadPool.WaitAll(wirs, 1500, true); + success = !timeout; + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait on a timeout for all + /// of them to complete. + /// + [Test] + public void WaitAllWithTimeoutFailure() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + bool timeout = !SmartThreadPool.WaitAll(wirs, 10, true); + success = timeout; + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait on a timeout for any + /// of them to complete. + /// + [Test] + public void WaitAnyWithTimeoutSuccess() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + int index = SmartThreadPool.WaitAny(wirs, 1500, true); + + success = (index != WaitHandle.WaitTimeout); + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue several work items and then wait on a timeout for any + /// of them to complete. + /// + [Test] + public void WaitAnyWithTimeoutFailure() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = true; + + IWorkItemResult [] wirs = new IWorkItemResult[5]; + + for(int i = 0; i < wirs.Length; ++i) + { + wirs[i] = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + } + + int index = SmartThreadPool.WaitAny(wirs, 10, true); + + success = (index == WaitHandle.WaitTimeout); + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + private object DoSomeWork(object state) + { + Thread.Sleep(1000); + return 1; + } + } +} diff --git a/STPTests/TestWIGPostExecute.cs b/STPTests/TestWIGPostExecute.cs new file mode 100644 index 0000000..fe42508 --- /dev/null +++ b/STPTests/TestWIGPostExecute.cs @@ -0,0 +1,231 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace WorkItemsGroupTests +{ + /// + /// Summary description for DoTestPostExecute. + /// + [TestFixture] + [Category("WorkItemsGroup")] + public class TestPostExecution + { + /// + /// + /// + [Test] + public void DefaultPostExecute_AlwaysCall() + { + Assert.IsTrue(DoTestDefaultPostExecute(CallToPostExecute.Always, true)); + } + + [Test] + public void DefaultPostExecute_NeverCall() + { + Assert.IsTrue(DoTestDefaultPostExecute(CallToPostExecute.Never, false)); + } + + [Test] + public void DefaultPostExecute_CallWhenCanceled() + { + Assert.IsTrue(DoTestDefaultPostExecute(CallToPostExecute.WhenWorkItemCanceled, false)); + } + + [Test] + public void DefaultPostExecute_CallWhenNotCanceled() + { + Assert.IsTrue(DoTestDefaultPostExecute(CallToPostExecute.WhenWorkItemNotCanceled, true)); + } + + /// + /// + /// + [Test] + public void PostExecute_AlwaysCall() + { + Assert.IsTrue(DoTestPostExecute(CallToPostExecute.Always, true)); + } + + [Test] + public void PostExecute_NeverCall() + { + Assert.IsTrue(DoTestPostExecute(CallToPostExecute.Never, false)); + } + + [Test] + public void PostExecute_CallWhenCanceled() + { + Assert.IsTrue(DoTestPostExecute(CallToPostExecute.WhenWorkItemCanceled, false)); + } + + [Test] + public void PostExecute_CallWhenNotCanceled() + { + Assert.IsTrue(DoTestPostExecute(CallToPostExecute.WhenWorkItemNotCanceled, true)); + } + + /// + /// + /// + [Test] + public void PostExecuteWithCancel_AlwaysCall() + { + Assert.IsTrue(DoTestPostExecuteWithCancel(CallToPostExecute.Always, true)); + } + + [Test] + public void PostExecuteWithCancel_NeverCall() + { + Assert.IsTrue(DoTestPostExecuteWithCancel(CallToPostExecute.Never, false)); + } + + [Test] + public void PostExecuteWithCancel_CallWhenCanceled() + { + Assert.IsTrue(DoTestPostExecuteWithCancel(CallToPostExecute.WhenWorkItemCanceled, true)); + } + + [Test] + public void PostExecuteWithCancel_CallWhenNotCanceled() + { + Assert.IsTrue(DoTestPostExecuteWithCancel(CallToPostExecute.WhenWorkItemNotCanceled, false)); + } + + + private class PostExecuteResult + { + public ManualResetEvent wh = new ManualResetEvent(false); + } + + /// + /// Example of how to use the post execute callback + /// + private bool DoTestDefaultPostExecute(CallToPostExecute callToPostExecute, bool answer) + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + WIGStartInfo wigStartInfo = new WIGStartInfo(); + wigStartInfo.CallToPostExecute = callToPostExecute; + wigStartInfo.PostExecuteWorkItemCallback = new PostExecuteWorkItemCallback(this.DoSomePostExecuteWork); + + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue, wigStartInfo); + + bool success = false; + + PostExecuteResult postExecuteResult = new PostExecuteResult(); + + IWorkItemResult wir = + workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + postExecuteResult); + + if (!wir.IsCompleted) + { + int result = (int)wir.GetResult(); + success = (1 == result); + success = success && (postExecuteResult.wh.WaitOne(1000, true) == answer); + } + + smartThreadPool.Shutdown(); + + return success; + } + + + /// + /// Example of how to use the post execute callback + /// + private bool DoTestPostExecute(CallToPostExecute callToPostExecute, bool answer) + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = false; + + PostExecuteResult postExecuteResult = new PostExecuteResult(); + + IWorkItemResult wir = + workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + postExecuteResult, + new PostExecuteWorkItemCallback(this.DoSomePostExecuteWork), + callToPostExecute); + + if (!wir.IsCompleted) + { + int result = (int)wir.GetResult(); + success = (1 == result); + success = success && (postExecuteResult.wh.WaitOne(1000, true) == answer); + } + + smartThreadPool.Shutdown(); + + return success; + } + + /// + /// Example of how to queue a work item and then cancel it while it is in the queue. + /// + private bool DoTestPostExecuteWithCancel(CallToPostExecute callToPostExecute, bool answer) + { + // Create a SmartThreadPool with only one thread. + // It just to show how to use the work item canceling feature + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(1); + + bool success = false; + PostExecuteResult postExecuteResult = new PostExecuteResult(); + + // Queue a work item that will occupy the thread in the pool + workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + null); + + // Queue another work item that will wait for the first to complete + IWorkItemResult wir = + workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + postExecuteResult, + new PostExecuteWorkItemCallback(this.DoSomePostExecuteWork), + callToPostExecute); + + + // Wait a while for the thread pool to start executing the first work item + Thread.Sleep(100); + + // Cancel the second work item while it still in the queue + if (wir.Cancel()) + { + success = (postExecuteResult.wh.WaitOne(1000, true) == answer); + } + + smartThreadPool.Shutdown(); + + return success; + } + + private object DoSomeWork(object state) + { + Thread.Sleep(1000); + return 1; + } + + private void DoSomePostExecuteWork(IWorkItemResult wir) + { + PostExecuteResult postExecuteResult = wir.State as PostExecuteResult; + postExecuteResult.wh.Set(); + } + + private object SignalCancel(object state) + { + ManualResetEvent cancelWaitHandle = state as ManualResetEvent; + Thread.Sleep(250); + cancelWaitHandle.Set(); + return null; + } + } +} diff --git a/STPTests/TestWIGStateDispose.cs b/STPTests/TestWIGStateDispose.cs new file mode 100644 index 0000000..bf3949b --- /dev/null +++ b/STPTests/TestWIGStateDispose.cs @@ -0,0 +1,148 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +using Amib.Threading; + +namespace WorkItemsGroupTests +{ + public class CallerState + { + private int _val = 0; + + public int Value + { + get { return _val; } + } + + protected void IncValue() + { + ++_val; + } + } + + public class NonDisposableCallerState : CallerState + { + public NonDisposableCallerState() + { + IncValue(); + } + } + + public class DisposableCallerState : CallerState, IDisposable + { + public DisposableCallerState() + { + IncValue(); + } + + #region IDisposable Members + + public void Dispose() + { + IncValue(); + } + + #endregion + } + + + /// + /// Summary description for StateDisposeExample. + /// + [TestFixture] + [Category("WorkItemsGroup")] + public class TestStateDispose + { + public TestStateDispose() + { + } + + /// + /// Example of non disposable caller state + /// + [Test] + public void DisposeCallerState() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + WIGStartInfo wigStartInfo = new WIGStartInfo(); + wigStartInfo.DisposeOfStateObjects = true; + + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue, wigStartInfo); + + CallerState nonDisposableCallerState = new NonDisposableCallerState(); + CallerState disposableCallerState = new DisposableCallerState(); + + IWorkItemResult wir1 = + workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + nonDisposableCallerState); + + IWorkItemResult wir2 = + workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + disposableCallerState); + + wir1.GetResult(); + Assert.AreEqual(1, nonDisposableCallerState.Value); + + wir2.GetResult(); + + // Wait a little bit for the working thread to call dispose on the + // work item's state. + workItemsGroup.WaitForIdle(); + + Assert.AreEqual(2, disposableCallerState.Value); + + smartThreadPool.Shutdown(); + } + + /// + /// Example of non disposable caller state + /// + [Test] + public void DontDisposeCallerState() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + + WIGStartInfo wigStartInfo = new WIGStartInfo(); + wigStartInfo.DisposeOfStateObjects = false; + + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue, wigStartInfo); + + bool success = false; + + CallerState nonDisposableCallerState = new NonDisposableCallerState(); + CallerState disposableCallerState = new DisposableCallerState(); + + IWorkItemResult wir1 = + workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + nonDisposableCallerState); + + IWorkItemResult wir2 = + workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + disposableCallerState); + + wir1.GetResult(); + success = (1 == nonDisposableCallerState.Value); + + wir2.GetResult(); + + success = success && (1 == disposableCallerState.Value); + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + private object DoSomeWork(object state) + { + Thread.Sleep(1000); + return 1; + } + } +} diff --git a/STPTests/TestWIGWaitForIdle.cs b/STPTests/TestWIGWaitForIdle.cs new file mode 100644 index 0000000..3a6841b --- /dev/null +++ b/STPTests/TestWIGWaitForIdle.cs @@ -0,0 +1,109 @@ +using System; +using System.Threading; +using System.Diagnostics; + +using NUnit.Framework; + +using Amib.Threading; + +namespace WorkItemsGroupTests +{ + /// + /// Summary description for TestWaitForIdle. + /// + [TestFixture] + [Category("WorkItemsGroup")] + public class TestWaitForIdle + { + public TestWaitForIdle() + { + } + + /// + /// Example of waiting for idle + /// + [Test] + public void WaitForIdle() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 25, 0); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = false; + + for(int i = 0; i < 100; ++i) + { + workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + null); + } + + success = !workItemsGroup.WaitForIdle(3500); + success = success && workItemsGroup.WaitForIdle(1000); + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + [Test] + public void WaitForIdleOnSTPThread() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 25, 0); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + IWorkItemResult wir = workItemsGroup.QueueWorkItem( + new WorkItemCallback(this.DoWaitForIdle), + workItemsGroup); + + Exception e; + wir.GetResult(out e); + + smartThreadPool.Shutdown(); + + Assert.IsNotNull(e); + } + + [Test] + public void WaitForIdleOnSTPThreadForAnotherWorkItemsGroup() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 25, 0); + IWorkItemsGroup workItemsGroup1 = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + IWorkItemsGroup workItemsGroup2 = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + workItemsGroup1.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + null); + + workItemsGroup1.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + null); + + IWorkItemResult wir = workItemsGroup2.QueueWorkItem( + new WorkItemCallback(this.DoWaitForIdle), + workItemsGroup1); + + Exception e; + wir.GetResult(out e); + + smartThreadPool.Shutdown(); + + Assert.IsNull(e); + } + + + private int x = 0; + private object DoSomeWork(object state) + { + Debug.WriteLine(Interlocked.Increment(ref x)); + Thread.Sleep(1000); + return 1; + } + + private object DoWaitForIdle(object state) + { + IWorkItemsGroup workItemsGroup = state as IWorkItemsGroup; + workItemsGroup.WaitForIdle(); + return null; + } + } +} diff --git a/STPTests/TestWaitForIdle.cs b/STPTests/TestWaitForIdle.cs new file mode 100644 index 0000000..9731fc8 --- /dev/null +++ b/STPTests/TestWaitForIdle.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading; +using System.Diagnostics; + +using NUnit.Framework; + +using Amib.Threading; + +namespace SmartThreadPoolTests +{ + /// + /// Summary description for TestWaitForIdle. + /// + [TestFixture] + [Category("TestWaitForIdle")] + public class TestWaitForIdle + { + public TestWaitForIdle() + { + } + + /// + /// Example of waiting for idle + /// + [Test] + public void WaitForIdle() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 25, 0); + + bool success = false; + + for(int i = 0; i < 100; ++i) + { + smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoSomeWork), + null); + } + + success = !smartThreadPool.WaitForIdle(3500); + success = success && smartThreadPool.WaitForIdle(1000); + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + [Test] + public void WaitForIdleOnWrongThread() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 25, 0); + + IWorkItemResult wir = smartThreadPool.QueueWorkItem( + new WorkItemCallback(this.DoWaitForIdle), + smartThreadPool); + + Exception e; + wir.GetResult(out e); + + smartThreadPool.Shutdown(); + + Assert.IsTrue(e is NotSupportedException); + } + + + private int x = 0; + private object DoSomeWork(object state) + { + Debug.WriteLine(Interlocked.Increment(ref x)); + Thread.Sleep(1000); + return 1; + } + + private object DoWaitForIdle(object state) + { + SmartThreadPool smartThreadPool = state as SmartThreadPool; + smartThreadPool.WaitForIdle(); + return null; + } + } +} diff --git a/STPTests/TestWorkItemsGroups.cs b/STPTests/TestWorkItemsGroups.cs new file mode 100644 index 0000000..fd6db09 --- /dev/null +++ b/STPTests/TestWorkItemsGroups.cs @@ -0,0 +1,190 @@ +using System; +using System.Threading; +using System.Diagnostics; + +using NUnit.Framework; + +using Amib.Threading; + +namespace WorkItemsGroupTests +{ + /// + /// Summary description for TestWorkItemsGroups. + /// + [TestFixture] + [Category("TestWorkItemsGroups")] + public class TestWorkItemsGroups + { + public TestWorkItemsGroups() + { + } + + [Test] + public void BlockingCall() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = false; + + IWorkItemResult wir = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + if (!wir.IsCompleted) + { + int result = (int)wir.GetResult(); + success = (1 == result); + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + + /// + /// Example of how to queue a work item and then wait on a timeout for the result. + /// + [Test] + public void Timeout() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = false; + + IWorkItemResult wir = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + try + { + wir.GetResult(500, true); + } + catch (WorkItemTimeoutException) + { + success = true; + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to queue a work item and then cancel it while it is in the queue. + /// + [Test] + public void WorkItemCanceling() + { + // Create a SmartThreadPool with only one thread. + // It just to show how to use the work item canceling feature + SmartThreadPool smartThreadPool = new SmartThreadPool(10*1000, 1); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + bool success = false; + + // Queue a work item that will occupy the thread in the pool + IWorkItemResult wir1 = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + // Queue another work item that will wait for the first to complete + IWorkItemResult wir2 = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + // Wait a while for the thread pool to start executing the first work item + Thread.Sleep(100); + + // The first work item cannot be canceled since it is currently executing + if (!wir1.Cancel()) + { + // Cancel the second work item while it still in the queue + if (wir2.Cancel()) + { + try + { + // Retreiving result of a canceled work item throws an exception + wir2.GetResult(); + } + catch (WorkItemCancelException) + { + success = true; + } + } + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + /// + /// Example of how to interrupt the waiting for a work item to complete. + /// + [Test] + public void WorkItemWaitCanceling() + { + SmartThreadPool smartThreadPool = new SmartThreadPool(); + IWorkItemsGroup workItemsGroup = smartThreadPool.CreateWorkItemsGroup(int.MaxValue); + + ManualResetEvent cancelWaitHandle = new ManualResetEvent(false); + + bool success = false; + + // Queue a work item that will occupy the thread in the pool + IWorkItemResult wir1 = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.DoSomeWork), null); + + // Queue another work item that will wait for the first to complete + IWorkItemResult wir2 = + workItemsGroup.QueueWorkItem(new WorkItemCallback(this.SignalCancel), cancelWaitHandle); + + try + { + wir1.GetResult(System.Threading.Timeout.Infinite, true, cancelWaitHandle); + } + catch (WorkItemTimeoutException) + { + success = true; + } + + smartThreadPool.Shutdown(); + + Assert.IsTrue(success); + } + + private object DoSomeWork(object state) + { + Thread.Sleep(1000); + return 1; + } + + private object SignalCancel(object state) + { + ManualResetEvent cancelWaitHandle = state as ManualResetEvent; + Thread.Sleep(250); + cancelWaitHandle.Set(); + return null; + } + + [Test] + public void Concurrency() + { + } + + [Test] + public void WaitForIdle() + { + } + + [Test] + public void OnIdleEvent() + { + } + + [Test] + public void MultipleGroups() + { + } + + } +} diff --git a/STPTests/TestWorkItemsQueue.cs b/STPTests/TestWorkItemsQueue.cs new file mode 100644 index 0000000..837a7f8 --- /dev/null +++ b/STPTests/TestWorkItemsQueue.cs @@ -0,0 +1,54 @@ +using System; + +using NUnit.Framework; + +using Amib.Threading; +using Amib.Threading.Internal; + +using SmartThreadPoolTests; + +namespace PriorityQueueTests +{ + /// + /// Summary description for TestWorkItemsQueue. + /// + [TestFixture] + [Category("TestWorkItemsQueue")] + public class TestWorkItemsQueue + { + public TestWorkItemsQueue() + { + } + + [Test] + public void Init() + { + } + + [Test] + public void IdempotenceWaiterEntry() + { + WorkItemsQueue q = new WorkItemsQueue(); + + Assert.AreEqual(0, q.WaitersCount); + + WorkItemsQueue.WaiterEntry we1 = new Amib.Threading.Internal.WorkItemsQueue.WaiterEntry(); + q.PushWaiter(we1); + + Assert.AreEqual(1, q.WaitersCount); + + q.PushWaiter(we1); + + Assert.AreEqual(1, q.WaitersCount); + + WorkItemsQueue.WaiterEntry we2 = new Amib.Threading.Internal.WorkItemsQueue.WaiterEntry(); + q.PushWaiter(we2); + + Assert.AreEqual(2, q.WaitersCount); + + q.PushWaiter(we2); + + Assert.AreEqual(2, q.WaitersCount); + } + } +} diff --git a/SmartThreadPool.sln b/SmartThreadPool.sln new file mode 100644 index 0000000..89065a9 --- /dev/null +++ b/SmartThreadPool.sln @@ -0,0 +1,43 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartThreadPool", "SmartThreadPool\SmartThreadPool.csproj", "{8684FC56-A679-4E2E-8F96-E172FB062EB6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STPExamples", "STPExamples\STPExamples.csproj", "{AE943A5A-7CFD-4E0D-BA51-FB763AAEA9A3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STPTests", "STPTests\STPTests.csproj", "{6A3E4DBF-12AD-4636-ACB3-24B5172FAE03}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UsageControl", "UsageControl\UsageControl.csproj", "{C11A4561-CCB5-4C96-8DF2-B804031D89D8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestSmartThreadPool", "TestSmartThreadPool\TestSmartThreadPool.csproj", "{976DB12F-9198-4AD9-981A-1652615C9B0D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8684FC56-A679-4E2E-8F96-E172FB062EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8684FC56-A679-4E2E-8F96-E172FB062EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8684FC56-A679-4E2E-8F96-E172FB062EB6}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {8684FC56-A679-4E2E-8F96-E172FB062EB6}.Release|Any CPU.Build.0 = Debug|Any CPU + {AE943A5A-7CFD-4E0D-BA51-FB763AAEA9A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE943A5A-7CFD-4E0D-BA51-FB763AAEA9A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE943A5A-7CFD-4E0D-BA51-FB763AAEA9A3}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {AE943A5A-7CFD-4E0D-BA51-FB763AAEA9A3}.Release|Any CPU.Build.0 = Debug|Any CPU + {6A3E4DBF-12AD-4636-ACB3-24B5172FAE03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A3E4DBF-12AD-4636-ACB3-24B5172FAE03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A3E4DBF-12AD-4636-ACB3-24B5172FAE03}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {6A3E4DBF-12AD-4636-ACB3-24B5172FAE03}.Release|Any CPU.Build.0 = Debug|Any CPU + {C11A4561-CCB5-4C96-8DF2-B804031D89D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C11A4561-CCB5-4C96-8DF2-B804031D89D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C11A4561-CCB5-4C96-8DF2-B804031D89D8}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {C11A4561-CCB5-4C96-8DF2-B804031D89D8}.Release|Any CPU.Build.0 = Debug|Any CPU + {976DB12F-9198-4AD9-981A-1652615C9B0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {976DB12F-9198-4AD9-981A-1652615C9B0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {976DB12F-9198-4AD9-981A-1652615C9B0D}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {976DB12F-9198-4AD9-981A-1652615C9B0D}.Release|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/SmartThreadPool/AssemblyInfo.cs b/SmartThreadPool/AssemblyInfo.cs new file mode 100644 index 0000000..471d9c4 --- /dev/null +++ b/SmartThreadPool/AssemblyInfo.cs @@ -0,0 +1,61 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: CLSCompliant(true)] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/SmartThreadPool/CallerThreadContext.cs b/SmartThreadPool/CallerThreadContext.cs new file mode 100644 index 0000000..9fe7941 --- /dev/null +++ b/SmartThreadPool/CallerThreadContext.cs @@ -0,0 +1,132 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Reflection; +using System.Web; +using System.Runtime.Remoting.Messaging; + + +namespace Amib.Threading +{ + #region CallerThreadContext class + + /// + /// This class stores the caller call context in order to restore + /// it when the work item is executed in the thread pool environment. + /// + internal class CallerThreadContext + { + #region Prepare reflection information + + // Cached type information. + private static MethodInfo getLogicalCallContextMethodInfo = + typeof(Thread).GetMethod("GetLogicalCallContext", BindingFlags.Instance | BindingFlags.NonPublic); + + private static MethodInfo setLogicalCallContextMethodInfo = + typeof(Thread).GetMethod("SetLogicalCallContext", BindingFlags.Instance | BindingFlags.NonPublic); + + private static string HttpContextSlotName = GetHttpContextSlotName(); + + private static string GetHttpContextSlotName() + { + FieldInfo fi = typeof(HttpContext).GetField("CallContextSlotName", BindingFlags.Static | BindingFlags.NonPublic); + + if( fi != null ) + return (string)fi.GetValue(null); + else // Use the default "HttpContext" slot name + return "HttpContext"; + } + + #endregion + + #region Private fields + + private HttpContext _httpContext = null; + private LogicalCallContext _callContext = null; + + #endregion + + /// + /// Constructor + /// + private CallerThreadContext() + { + } + + public bool CapturedCallContext + { + get + { + return (null != _callContext); + } + } + + public bool CapturedHttpContext + { + get + { + return (null != _httpContext); + } + } + + /// + /// Captures the current thread context + /// + /// + public static CallerThreadContext Capture( + bool captureCallContext, + bool captureHttpContext) + { + Debug.Assert(captureCallContext || captureHttpContext); + + CallerThreadContext callerThreadContext = new CallerThreadContext(); + + // TODO: In NET 2.0, redo using the new feature of ExecutionContext class - Capture() + // Capture Call Context + if(captureCallContext && (getLogicalCallContextMethodInfo != null)) + { + callerThreadContext._callContext = (LogicalCallContext)getLogicalCallContextMethodInfo.Invoke(Thread.CurrentThread, null); + if (callerThreadContext._callContext != null) + { + callerThreadContext._callContext = (LogicalCallContext)callerThreadContext._callContext.Clone(); + } + } + + // Capture httpContext + if (captureHttpContext && (null != HttpContext.Current)) + { + callerThreadContext._httpContext = HttpContext.Current; + } + + return callerThreadContext; + } + + /// + /// Applies the thread context stored earlier + /// + /// + public static void Apply(CallerThreadContext callerThreadContext) + { + if (null == callerThreadContext) + { + throw new ArgumentNullException("callerThreadContext"); + } + + // Todo: In NET 2.0, redo using the new feature of ExecutionContext class - Run() + // Restore call context + if ((callerThreadContext._callContext != null) && (setLogicalCallContextMethodInfo != null)) + { + setLogicalCallContextMethodInfo.Invoke(Thread.CurrentThread, new object[] { callerThreadContext._callContext }); + } + + // Restore HttpContext + if (callerThreadContext._httpContext != null) + { + HttpContext.Current = callerThreadContext._httpContext; + } + } + } + + #endregion + +} diff --git a/SmartThreadPool/Exceptions.cs b/SmartThreadPool/Exceptions.cs new file mode 100644 index 0000000..6ebca30 --- /dev/null +++ b/SmartThreadPool/Exceptions.cs @@ -0,0 +1,81 @@ +// Ami Bar +// amibar@gmail.com + +using System; +using System.Runtime.Serialization; + +namespace Amib.Threading +{ + #region Exceptions + + /// + /// Represents an exception in case IWorkItemResult.GetResult has been canceled + /// + [Serializable] + public sealed class WorkItemCancelException : ApplicationException + { + public WorkItemCancelException() : base() + { + } + + public WorkItemCancelException(string message) : base(message) + { + } + + public WorkItemCancelException(string message, Exception e) : base(message, e) + { + } + + public WorkItemCancelException(SerializationInfo si, StreamingContext sc) : base(si, sc) + { + } + } + + /// + /// Represents an exception in case IWorkItemResult.GetResult has been timed out + /// + [Serializable] + public sealed class WorkItemTimeoutException : ApplicationException + { + public WorkItemTimeoutException() : base() + { + } + + public WorkItemTimeoutException(string message) : base(message) + { + } + + public WorkItemTimeoutException(string message, Exception e) : base(message, e) + { + } + + public WorkItemTimeoutException(SerializationInfo si, StreamingContext sc) : base(si, sc) + { + } + } + + /// + /// Represents an exception in case IWorkItemResult.GetResult has been timed out + /// + [Serializable] + public sealed class WorkItemResultException : ApplicationException + { + public WorkItemResultException() : base() + { + } + + public WorkItemResultException(string message) : base(message) + { + } + + public WorkItemResultException(string message, Exception e) : base(message, e) + { + } + + public WorkItemResultException(SerializationInfo si, StreamingContext sc) : base(si, sc) + { + } + } + + #endregion +} diff --git a/SmartThreadPool/Interfaces.cs b/SmartThreadPool/Interfaces.cs new file mode 100644 index 0000000..9170186 --- /dev/null +++ b/SmartThreadPool/Interfaces.cs @@ -0,0 +1,269 @@ +// Ami Bar +// amibar@gmail.com + +using System; +using System.Threading; + +namespace Amib.Threading +{ + #region Delegates + + /// + /// A delegate that represents the method to run as the work item + /// + /// A state object for the method to run + public delegate object WorkItemCallback(object state); + + /// + /// A delegate to call after the WorkItemCallback completed + /// + /// The work item result object + public delegate void PostExecuteWorkItemCallback(IWorkItemResult wir); + + /// + /// A delegate to call when a WorkItemsGroup becomes idle + /// + /// A reference to the WorkItemsGroup that became idle + public delegate void WorkItemsGroupIdleHandler(IWorkItemsGroup workItemsGroup); + + #endregion + + #region WorkItem Priority + + public enum WorkItemPriority + { + Lowest, + BelowNormal, + Normal, + AboveNormal, + Highest, + } + + #endregion + + #region IHasWorkItemPriority interface + + public interface IHasWorkItemPriority + { + WorkItemPriority WorkItemPriority { get; } + } + + #endregion + + #region IWorkItemsGroup interface + + /// + /// IWorkItemsGroup interface + /// + public interface IWorkItemsGroup + { + /// + /// Get/Set the name of the WorkItemsGroup + /// + string Name { get; set; } + + IWorkItemResult QueueWorkItem(WorkItemCallback callback); + IWorkItemResult QueueWorkItem(WorkItemCallback callback, WorkItemPriority workItemPriority); + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state); + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, WorkItemPriority workItemPriority); + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, PostExecuteWorkItemCallback postExecuteWorkItemCallback); + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, PostExecuteWorkItemCallback postExecuteWorkItemCallback, WorkItemPriority workItemPriority); + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, PostExecuteWorkItemCallback postExecuteWorkItemCallback, CallToPostExecute callToPostExecute); + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, PostExecuteWorkItemCallback postExecuteWorkItemCallback, CallToPostExecute callToPostExecute, WorkItemPriority workItemPriority); + + IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback); + IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback, object state); + + void WaitForIdle(); + bool WaitForIdle(TimeSpan timeout); + bool WaitForIdle(int millisecondsTimeout); + + int WaitingCallbacks { get; } + event WorkItemsGroupIdleHandler OnIdle; + + void Cancel(); + void Start(); + } + + #endregion + + #region CallToPostExecute enumerator + + [Flags] + public enum CallToPostExecute + { + Never = 0x00, + WhenWorkItemCanceled = 0x01, + WhenWorkItemNotCanceled = 0x02, + Always = WhenWorkItemCanceled | WhenWorkItemNotCanceled, + } + + #endregion + + #region IWorkItemResult interface + + /// + /// IWorkItemResult interface + /// + public interface IWorkItemResult + { + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits. + /// + /// The result of the work item + object GetResult(); + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout. + /// + /// The result of the work item + /// On timeout throws WorkItemTimeoutException + object GetResult( + int millisecondsTimeout, + bool exitContext); + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout. + /// + /// The result of the work item + /// On timeout throws WorkItemTimeoutException + object GetResult( + TimeSpan timeout, + bool exitContext); + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout or until the cancelWaitHandle is signaled. + /// + /// Timeout in milliseconds, or -1 for infinite + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// A cancel wait handle to interrupt the blocking if needed + /// The result of the work item + /// On timeout throws WorkItemTimeoutException + /// On cancel throws WorkItemCancelException + object GetResult( + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle); + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout or until the cancelWaitHandle is signaled. + /// + /// The result of the work item + /// On timeout throws WorkItemTimeoutException + /// On cancel throws WorkItemCancelException + object GetResult( + TimeSpan timeout, + bool exitContext, + WaitHandle cancelWaitHandle); + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits. + /// + /// Filled with the exception if one was thrown + /// The result of the work item + object GetResult(out Exception e); + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout. + /// + /// Filled with the exception if one was thrown + /// The result of the work item + /// On timeout throws WorkItemTimeoutException + object GetResult( + int millisecondsTimeout, + bool exitContext, + out Exception e); + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout. + /// + /// Filled with the exception if one was thrown + /// The result of the work item + /// On timeout throws WorkItemTimeoutException + object GetResult( + TimeSpan timeout, + bool exitContext, + out Exception e); + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout or until the cancelWaitHandle is signaled. + /// + /// Timeout in milliseconds, or -1 for infinite + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// A cancel wait handle to interrupt the blocking if needed + /// Filled with the exception if one was thrown + /// The result of the work item + /// On timeout throws WorkItemTimeoutException + /// On cancel throws WorkItemCancelException + object GetResult( + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle, + out Exception e); + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout or until the cancelWaitHandle is signaled. + /// + /// The result of the work item + /// Filled with the exception if one was thrown + /// On timeout throws WorkItemTimeoutException + /// On cancel throws WorkItemCancelException + object GetResult( + TimeSpan timeout, + bool exitContext, + WaitHandle cancelWaitHandle, + out Exception e); + + /// + /// Gets an indication whether the asynchronous operation has completed. + /// + bool IsCompleted { get; } + + /// + /// Gets an indication whether the asynchronous operation has been canceled. + /// + bool IsCanceled { get; } + + /// + /// Gets a user-defined object that qualifies or contains information about an asynchronous operation. + /// + object State { get; } + + /// + /// Cancel the work item if it didn't start running yet. + /// + /// Returns true on success or false if the work item is in progress or already completed + bool Cancel(); + + /// + /// Get the work item's priority + /// + WorkItemPriority WorkItemPriority { get; } + + /// + /// Return the result, same as GetResult() + /// + object Result { get; } + + /// + /// Returns the exception if occured otherwise returns null. + /// + object Exception { get; } + } + + #endregion +} diff --git a/SmartThreadPool/PriorityQueue.cs b/SmartThreadPool/PriorityQueue.cs new file mode 100644 index 0000000..8ff9da0 --- /dev/null +++ b/SmartThreadPool/PriorityQueue.cs @@ -0,0 +1,240 @@ +// Ami Bar +// amibar@gmail.com + +using System; +using System.Collections; +using System.Diagnostics; + +namespace Amib.Threading.Internal +{ + #region PriorityQueue class + + /// + /// PriorityQueue class + /// This class is not thread safe because we use external lock + /// + public sealed class PriorityQueue : IEnumerable + { + #region Private members + + /// + /// The number of queues, there is one for each type of priority + /// + private const int _queuesCount = WorkItemPriority.Highest-WorkItemPriority.Lowest+1; + + /// + /// Work items queues. There is one for each type of priority + /// + private Queue [] _queues = new Queue[_queuesCount]; + + /// + /// The total number of work items within the queues + /// + private int _workItemsCount = 0; + + /// + /// Use with IEnumerable interface + /// + private int _version = 0; + + #endregion + + #region Contructor + + public PriorityQueue() + { + for(int i = 0; i < _queues.Length; ++i) + { + _queues[i] = new Queue(); + } + } + + #endregion + + #region Methods + + /// + /// Enqueue a work item. + /// + /// A work item + public void Enqueue(IHasWorkItemPriority workItem) + { + Debug.Assert(null != workItem); + + int queueIndex = _queuesCount-(int)workItem.WorkItemPriority-1; + Debug.Assert(queueIndex >= 0); + Debug.Assert(queueIndex < _queuesCount); + + _queues[queueIndex].Enqueue(workItem); + ++_workItemsCount; + ++_version; + } + + /// + /// Dequeque a work item. + /// + /// Returns the next work item + public IHasWorkItemPriority Dequeue() + { + IHasWorkItemPriority workItem = null; + + if(_workItemsCount > 0) + { + int queueIndex = GetNextNonEmptyQueue(-1); + Debug.Assert(queueIndex >= 0); + workItem = _queues[queueIndex].Dequeue() as IHasWorkItemPriority; + Debug.Assert(null != workItem); + --_workItemsCount; + ++_version; + } + + return workItem; + } + + /// + /// Find the next non empty queue starting at queue queueIndex+1 + /// + /// The index-1 to start from + /// + /// The index of the next non empty queue or -1 if all the queues are empty + /// + private int GetNextNonEmptyQueue(int queueIndex) + { + for(int i = queueIndex+1; i < _queuesCount; ++i) + { + if(_queues[i].Count > 0) + { + return i; + } + } + return -1; + } + + /// + /// The number of work items + /// + public int Count + { + get + { + return _workItemsCount; + } + } + + /// + /// Clear all the work items + /// + public void Clear() + { + if (_workItemsCount > 0) + { + foreach(Queue queue in _queues) + { + queue.Clear(); + } + _workItemsCount = 0; + ++_version; + } + } + + #endregion + + #region IEnumerable Members + + /// + /// Returns an enumerator to iterate over the work items + /// + /// Returns an enumerator + public IEnumerator GetEnumerator() + { + return new PriorityQueueEnumerator(this); + } + + #endregion + + #region PriorityQueueEnumerator + + /// + /// The class the implements the enumerator + /// + private class PriorityQueueEnumerator : IEnumerator + { + private PriorityQueue _priorityQueue; + private int _version; + private int _queueIndex; + private IEnumerator _enumerator; + + public PriorityQueueEnumerator(PriorityQueue priorityQueue) + { + _priorityQueue = priorityQueue; + _version = _priorityQueue._version; + _queueIndex = _priorityQueue.GetNextNonEmptyQueue(-1); + if (_queueIndex >= 0) + { + _enumerator = _priorityQueue._queues[_queueIndex].GetEnumerator(); + } + else + { + _enumerator = null; + } + } + + #region IEnumerator Members + + public void Reset() + { + _version = _priorityQueue._version; + _queueIndex = _priorityQueue.GetNextNonEmptyQueue(-1); + if (_queueIndex >= 0) + { + _enumerator = _priorityQueue._queues[_queueIndex].GetEnumerator(); + } + else + { + _enumerator = null; + } + } + + public object Current + { + get + { + Debug.Assert(null != _enumerator); + return _enumerator.Current; + } + } + + public bool MoveNext() + { + if (null == _enumerator) + { + return false; + } + + if(_version != _priorityQueue._version) + { + throw new InvalidOperationException("The collection has been modified"); + + } + if (!_enumerator.MoveNext()) + { + _queueIndex = _priorityQueue.GetNextNonEmptyQueue(_queueIndex); + if(-1 == _queueIndex) + { + return false; + } + _enumerator = _priorityQueue._queues[_queueIndex].GetEnumerator(); + _enumerator.MoveNext(); + return true; + } + return true; + } + + #endregion + } + + #endregion + } + + #endregion +} diff --git a/SmartThreadPool/STPPerformanceCounter.cs b/SmartThreadPool/STPPerformanceCounter.cs new file mode 100644 index 0000000..2ee88b4 --- /dev/null +++ b/SmartThreadPool/STPPerformanceCounter.cs @@ -0,0 +1,347 @@ +using System; +using System.Diagnostics; + +namespace Amib.Threading.Internal +{ + internal enum STPPerformanceCounterType + { + // Fields + ActiveThreads = 0, + InUseThreads = 1, + OverheadThreads = 2, + OverheadThreadsPercent = 3, + OverheadThreadsPercentBase = 4, + + WorkItems = 5, + WorkItemsInQueue = 6, + WorkItemsProcessed = 7, + + WorkItemsQueuedPerSecond = 8, + WorkItemsProcessedPerSecond = 9, + + AvgWorkItemWaitTime = 10, + AvgWorkItemWaitTimeBase = 11, + + AvgWorkItemProcessTime = 12, + AvgWorkItemProcessTimeBase = 13, + + WorkItemsGroups = 14, + + LastCounter = 14, + } + + + /// + /// Summary description for STPPerformanceCounter. + /// + internal class STPPerformanceCounter + { + // Fields + private PerformanceCounterType _pcType; + protected string _counterHelp; + protected string _counterName; + + // Methods + public STPPerformanceCounter( + string counterName, + string counterHelp, + PerformanceCounterType pcType) + { + this._counterName = counterName; + this._counterHelp = counterHelp; + this._pcType = pcType; + } + + public void AddCounterToCollection(CounterCreationDataCollection counterData) + { + CounterCreationData counterCreationData = new CounterCreationData( + _counterName, + _counterHelp, + _pcType); + + counterData.Add(counterCreationData); + } + + // Properties + public string Name + { + get + { + return _counterName; + } + } + } + + internal class STPPerformanceCounters + { + // Fields + internal STPPerformanceCounter[] _stpPerformanceCounters; + private static STPPerformanceCounters _instance; + internal const string _stpCategoryHelp = "SmartThreadPool performance counters"; + internal const string _stpCategoryName = "SmartThreadPool"; + + // Methods + static STPPerformanceCounters() + { + _instance = new STPPerformanceCounters(); + } + + private STPPerformanceCounters() + { + STPPerformanceCounter[] stpPerformanceCounters = new STPPerformanceCounter[] + { + new STPPerformanceCounter("Active threads", "The current number of available in the thread pool.", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("In use threads", "The current number of threads that execute a work item.", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("Overhead threads", "The current number of threads that are active, but are not in use.", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("% overhead threads", "The current number of threads that are active, but are not in use in percents.", PerformanceCounterType.RawFraction), + new STPPerformanceCounter("% overhead threads base", "The current number of threads that are active, but are not in use in percents.", PerformanceCounterType.RawBase), + + new STPPerformanceCounter("Work Items", "The number of work items in the Smart Thread Pool. Both queued and processed.", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("Work Items in queue", "The current number of work items in the queue", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("Work Items processed", "The number of work items already processed", PerformanceCounterType.NumberOfItems32), + + new STPPerformanceCounter("Work Items queued/sec", "The number of work items queued per second", PerformanceCounterType.RateOfCountsPerSecond32), + new STPPerformanceCounter("Work Items processed/sec", "The number of work items processed per second", PerformanceCounterType.RateOfCountsPerSecond32), + + new STPPerformanceCounter("Avg. Work Item wait time/sec", "The average time a work item supends in the queue waiting for its turn to execute.", PerformanceCounterType.AverageCount64), + new STPPerformanceCounter("Avg. Work Item wait time base", "The average time a work item supends in the queue waiting for its turn to execute.", PerformanceCounterType.AverageBase), + + new STPPerformanceCounter("Avg. Work Item process time/sec", "The average time it takes to process a work item.", PerformanceCounterType.AverageCount64), + new STPPerformanceCounter("Avg. Work Item process time base", "The average time it takes to process a work item.", PerformanceCounterType.AverageBase), + + new STPPerformanceCounter("Work Items Groups", "The current number of work item groups associated with the Smart Thread Pool.", PerformanceCounterType.NumberOfItems32), + }; + + _stpPerformanceCounters = stpPerformanceCounters; + SetupCategory(); + } + + private void SetupCategory() + { + if (!PerformanceCounterCategory.Exists(_stpCategoryName)) + { + CounterCreationDataCollection counters = new CounterCreationDataCollection(); + + for (int i = 0; i < _stpPerformanceCounters.Length; i++) + { + _stpPerformanceCounters[i].AddCounterToCollection(counters); + } + + PerformanceCounterCategory.Create( + _stpCategoryName, + _stpCategoryHelp, + PerformanceCounterCategoryType.MultiInstance, + counters); + + } + } + + // Properties + public static STPPerformanceCounters Instance + { + get + { + return _instance; + } + } + } + + internal class STPInstancePerformanceCounter : IDisposable + { + // Fields + private PerformanceCounter _pcs; + + // Methods + protected STPInstancePerformanceCounter() + { + } + + public STPInstancePerformanceCounter( + string instance, + STPPerformanceCounterType spcType) + { + STPPerformanceCounters counters = STPPerformanceCounters.Instance; + _pcs = new PerformanceCounter( + STPPerformanceCounters._stpCategoryName, + counters._stpPerformanceCounters[(int) spcType].Name, + instance, + false); + _pcs.RawValue = _pcs.RawValue; + } + + ~STPInstancePerformanceCounter() + { + Close(); + } + + public void Close() + { + if (_pcs != null) + { + _pcs.RemoveInstance(); + _pcs.Close(); + _pcs = null; + } + } + + public void Dispose() + { + Close(); + GC.SuppressFinalize(this); + } + + public virtual void Increment() + { + _pcs.Increment(); + } + + public virtual void IncrementBy(long val) + { + _pcs.IncrementBy(val); + } + + public virtual void Set(long val) + { + _pcs.RawValue = val; + } + } + + internal class STPInstanceNullPerformanceCounter : STPInstancePerformanceCounter + { + // Methods + public STPInstanceNullPerformanceCounter() {} + public override void Increment() {} + public override void IncrementBy(long value) {} + public override void Set(long val) {} + } + + internal interface ISTPInstancePerformanceCounters : IDisposable + { + void Close(); + void SampleThreads(long activeThreads, long inUseThreads); + void SampleWorkItems(long workItemsQueued, long workItemsProcessed); + void SampleWorkItemsWaitTime(TimeSpan workItemWaitTime); + void SampleWorkItemsProcessTime(TimeSpan workItemProcessTime); + } + + + internal class STPInstancePerformanceCounters : ISTPInstancePerformanceCounters, IDisposable + { + // Fields + private STPInstancePerformanceCounter[] _pcs; + private static STPInstancePerformanceCounter _stpInstanceNullPerformanceCounter; + + // Methods + static STPInstancePerformanceCounters() + { + _stpInstanceNullPerformanceCounter = new STPInstanceNullPerformanceCounter(); + } + + public STPInstancePerformanceCounters(string instance) + { + _pcs = new STPInstancePerformanceCounter[(int)STPPerformanceCounterType.LastCounter]; + STPPerformanceCounters counters = STPPerformanceCounters.Instance; + for (int i = 0; i < _pcs.Length; i++) + { + if (instance != null) + { + _pcs[i] = new STPInstancePerformanceCounter( + instance, + (STPPerformanceCounterType) i); + } + else + { + _pcs[i] = _stpInstanceNullPerformanceCounter; + } + } + } + + + public void Close() + { + if (null != _pcs) + { + for (int i = 0; i < _pcs.Length; i++) + { + if (null != _pcs[i]) + { + _pcs[i].Close(); + } + } + _pcs = null; + } + } + + ~STPInstancePerformanceCounters() + { + Close(); + } + + public void Dispose() + { + Close(); + GC.SuppressFinalize(this); + } + + private STPInstancePerformanceCounter GetCounter(STPPerformanceCounterType spcType) + { + return _pcs[(int) spcType]; + } + + public void SampleThreads(long activeThreads, long inUseThreads) + { + GetCounter(STPPerformanceCounterType.ActiveThreads).Set(activeThreads); + GetCounter(STPPerformanceCounterType.InUseThreads).Set(inUseThreads); + GetCounter(STPPerformanceCounterType.OverheadThreads).Set(activeThreads-inUseThreads); + + GetCounter(STPPerformanceCounterType.OverheadThreadsPercentBase).Set(activeThreads-inUseThreads); + GetCounter(STPPerformanceCounterType.OverheadThreadsPercent).Set(inUseThreads); + } + + public void SampleWorkItems(long workItemsQueued, long workItemsProcessed) + { + GetCounter(STPPerformanceCounterType.WorkItems).Set(workItemsQueued+workItemsProcessed); + GetCounter(STPPerformanceCounterType.WorkItemsInQueue).Set(workItemsQueued); + GetCounter(STPPerformanceCounterType.WorkItemsProcessed).Set(workItemsProcessed); + + GetCounter(STPPerformanceCounterType.WorkItemsQueuedPerSecond).Set(workItemsQueued); + GetCounter(STPPerformanceCounterType.WorkItemsProcessedPerSecond).Set(workItemsProcessed); + } + + public void SampleWorkItemsWaitTime(TimeSpan workItemWaitTime) + { + GetCounter(STPPerformanceCounterType.AvgWorkItemWaitTime).IncrementBy((long)workItemWaitTime.TotalMilliseconds); + GetCounter(STPPerformanceCounterType.AvgWorkItemWaitTimeBase).Increment(); + } + + public void SampleWorkItemsProcessTime(TimeSpan workItemProcessTime) + { + GetCounter(STPPerformanceCounterType.AvgWorkItemProcessTime).IncrementBy((long)workItemProcessTime.TotalMilliseconds); + GetCounter(STPPerformanceCounterType.AvgWorkItemProcessTimeBase).Increment(); + } + } + + internal class NullSTPInstancePerformanceCounters : ISTPInstancePerformanceCounters, IDisposable + { + static NullSTPInstancePerformanceCounters() + { + } + + private static NullSTPInstancePerformanceCounters _instance = new NullSTPInstancePerformanceCounters(null); + + public static NullSTPInstancePerformanceCounters Instance + { + get { return _instance; } + } + + public NullSTPInstancePerformanceCounters(string instance) {} + public void Close() {} + public void Dispose() {} + + public void SampleThreads(long activeThreads, long inUseThreads) {} + public void SampleWorkItems(long workItemsQueued, long workItemsProcessed) {} + public void SampleWorkItemsWaitTime(TimeSpan workItemWaitTime) {} + public void SampleWorkItemsProcessTime(TimeSpan workItemProcessTime) {} + } + +} diff --git a/SmartThreadPool/STPStartInfo.cs b/SmartThreadPool/STPStartInfo.cs new file mode 100644 index 0000000..c205a69 --- /dev/null +++ b/SmartThreadPool/STPStartInfo.cs @@ -0,0 +1,89 @@ +// Ami Bar +// amibar@gmail.com + +using System.Threading; + +namespace Amib.Threading +{ + /// + /// Summary description for STPStartInfo. + /// + public class STPStartInfo : WIGStartInfo + { + /// + /// Idle timeout in milliseconds. + /// If a thread is idle for _idleTimeout milliseconds then + /// it may quit. + /// + private int _idleTimeout; + + /// + /// The lower limit of threads in the pool. + /// + private int _minWorkerThreads; + + /// + /// The upper limit of threads in the pool. + /// + private int _maxWorkerThreads; + + /// + /// The priority of the threads in the pool + /// + private ThreadPriority _threadPriority; + + /// + /// If this field is not null then the performance counters are enabled + /// and use the string as the name of the instance. + /// + private string _pcInstanceName; + + public STPStartInfo() : base() + { + _idleTimeout = SmartThreadPool.DefaultIdleTimeout; + _minWorkerThreads = SmartThreadPool.DefaultMinWorkerThreads; + _maxWorkerThreads = SmartThreadPool.DefaultMaxWorkerThreads; + _threadPriority = SmartThreadPool.DefaultThreadPriority; + _pcInstanceName = SmartThreadPool.DefaultPerformanceCounterInstanceName; + } + + public STPStartInfo(STPStartInfo stpStartInfo) : base(stpStartInfo) + { + _idleTimeout = stpStartInfo._idleTimeout; + _minWorkerThreads = stpStartInfo._minWorkerThreads; + _maxWorkerThreads = stpStartInfo._maxWorkerThreads; + _threadPriority = stpStartInfo._threadPriority; + _pcInstanceName = stpStartInfo._pcInstanceName; + } + + public int IdleTimeout + { + get { return _idleTimeout; } + set { _idleTimeout = value; } + } + + public int MinWorkerThreads + { + get { return _minWorkerThreads; } + set { _minWorkerThreads = value; } + } + + public int MaxWorkerThreads + { + get { return _maxWorkerThreads; } + set { _maxWorkerThreads = value; } + } + + public ThreadPriority ThreadPriority + { + get { return _threadPriority; } + set { _threadPriority = value; } + } + + public string PerformanceCounterInstanceName + { + get { return _pcInstanceName; } + set { _pcInstanceName = value; } + } + } +} diff --git a/SmartThreadPool/SmartThreadPool.cs b/SmartThreadPool/SmartThreadPool.cs new file mode 100644 index 0000000..b627053 --- /dev/null +++ b/SmartThreadPool/SmartThreadPool.cs @@ -0,0 +1,1411 @@ +// Ami Bar +// amibar@gmail.com +// +// Smart thread pool in C#. +// 7 Aug 2004 - Initial release +// 14 Sep 2004 - Bug fixes +// 15 Oct 2004 - Added new features +// - Work items return result. +// - Support waiting synchronization for multiple work items. +// - Work items can be cancelled. +// - Passage of the caller thread’s context to the thread in the pool. +// - Minimal usage of WIN32 handles. +// - Minor bug fixes. +// 26 Dec 2004 - Changes: +// - Removed static constructors. +// - Added finalizers. +// - Changed Exceptions so they are serializable. +// - Fixed the bug in one of the SmartThreadPool constructors. +// - Changed the SmartThreadPool.WaitAll() so it will support any number of waiters. +// The SmartThreadPool.WaitAny() is still limited by the .NET Framework. +// - Added PostExecute with options on which cases to call it. +// - Added option to dispose of the state objects. +// - Added a WaitForIdle() method that waits until the work items queue is empty. +// - Added an STPStartInfo class for the initialization of the thread pool. +// - Changed exception handling so if a work item throws an exception it +// is rethrown at GetResult(), rather then firing an UnhandledException event. +// Note that PostExecute exception are always ignored. +// 25 Mar 2005 - Changes: +// - Fixed lost of work items bug +// 3 Jul 2005: Changes. +// - Fixed bug where Enqueue() throws an exception because PopWaiter() returned null, hardly reconstructed. +// 16 Aug 2005: Changes. +// - Fixed bug where the InUseThreads becomes negative when canceling work items. +// +// 31 Jan 2006 - Changes: +// - Added work items priority +// - Removed support of chained delegates in callbacks and post executes (nobody really use this) +// - Added work items groups +// - Added work items groups idle event +// - Changed SmartThreadPool.WaitAll() behavior so when it gets empty array +// it returns true rather then throwing an exception. +// - Added option to start the STP and the WIG as suspended +// - Exception behavior changed, the real exception is returned by an +// inner exception +// - Added option to keep the Http context of the caller thread. (Thanks to Steven T.) +// - Added performance counters +// - Added priority to the threads in the pool +// +// 13 Feb 2006 - Changes: +// - Added a call to the dispose of the Performance Counter so +// their won't be a Performance Counter leak. +// - Added exception catch in case the Performance Counters cannot +// be created. + +using System; +using System.Security; +using System.Threading; +using System.Collections; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +using Amib.Threading.Internal; + +namespace Amib.Threading +{ + #region SmartThreadPool class + /// + /// Smart thread pool class. + /// + public class SmartThreadPool : IWorkItemsGroup, IDisposable + { + #region Default Constants + + /// + /// Default minimum number of threads the thread pool contains. (0) + /// + public const int DefaultMinWorkerThreads = 0; + + /// + /// Default maximum number of threads the thread pool contains. (25) + /// + public const int DefaultMaxWorkerThreads = 25; + + /// + /// Default idle timeout in milliseconds. (One minute) + /// + public const int DefaultIdleTimeout = 60*1000; // One minute + + /// + /// Indicate to copy the security context of the caller and then use it in the call. (false) + /// + public const bool DefaultUseCallerCallContext = false; + + /// + /// Indicate to copy the HTTP context of the caller and then use it in the call. (false) + /// + public const bool DefaultUseCallerHttpContext = false; + + /// + /// Indicate to dispose of the state objects if they support the IDispose interface. (false) + /// + public const bool DefaultDisposeOfStateObjects = false; + + /// + /// The default option to run the post execute + /// + public const CallToPostExecute DefaultCallToPostExecute = CallToPostExecute.Always; + + /// + /// The default post execute method to run. + /// When null it means not to call it. + /// + public static readonly PostExecuteWorkItemCallback DefaultPostExecuteWorkItemCallback = null; + + /// + /// The default work item priority + /// + public const WorkItemPriority DefaultWorkItemPriority = WorkItemPriority.Normal; + + /// + /// The default is to work on work items as soon as they arrive + /// and not to wait for the start. + /// + public const bool DefaultStartSuspended = false; + + /// + /// The default is not to use the performance counters + /// + public static readonly string DefaultPerformanceCounterInstanceName = null; + + /// + /// The default thread priority + /// + public const ThreadPriority DefaultThreadPriority = ThreadPriority.Normal; + + #endregion + + #region Member Variables + + /// + /// Contains the name of this instance of SmartThreadPool. + /// Can be changed by the user. + /// + private string _name = "SmartThreadPool"; + + /// + /// Hashtable of all the threads in the thread pool. + /// + private Hashtable _workerThreads = Hashtable.Synchronized(new Hashtable()); + + /// + /// Queue of work items. + /// + private WorkItemsQueue _workItemsQueue = new WorkItemsQueue(); + + /// + /// Count the work items handled. + /// Used by the performance counter. + /// + private long _workItemsProcessed = 0; + + /// + /// Number of threads that currently work (not idle). + /// + private int _inUseWorkerThreads = 0; + + /// + /// Start information to use. + /// It is simpler than providing many constructors. + /// + private STPStartInfo _stpStartInfo = new STPStartInfo(); + + /// + /// Total number of work items that are stored in the work items queue + /// plus the work items that the threads in the pool are working on. + /// + private int _currentWorkItemsCount = 0; + + /// + /// Signaled when the thread pool is idle, i.e. no thread is busy + /// and the work items queue is empty + /// + private ManualResetEvent _isIdleWaitHandle = new ManualResetEvent(true); + + /// + /// An event to signal all the threads to quit immediately. + /// + private ManualResetEvent _shuttingDownEvent = new ManualResetEvent(false); + + /// + /// A flag to indicate the threads to quit. + /// + private bool _shutdown = false; + + /// + /// Counts the threads created in the pool. + /// It is used to name the threads. + /// + private int _threadCounter = 0; + + /// + /// Indicate that the SmartThreadPool has been disposed + /// + private bool _isDisposed = false; + + /// + /// Event to send that the thread pool is idle + /// + private event EventHandler _stpIdle; + + /// + /// On idle event + /// + //private event WorkItemsGroupIdleHandler _onIdle; + + /// + /// Holds all the WorkItemsGroup instaces that have at least one + /// work item int the SmartThreadPool + /// This variable is used in case of Shutdown + /// + private Hashtable _workItemsGroups = Hashtable.Synchronized(new Hashtable()); + + /// + /// A reference from each thread in the thread pool to its SmartThreadPool + /// object container. + /// With this variable a thread can know whatever it belongs to a + /// SmartThreadPool. + /// + [ThreadStatic] + private static SmartThreadPool _smartThreadPool; + + /// + /// A reference to the current work item a thread from the thread pool + /// is executing. + /// + [ThreadStatic] + private static WorkItem _currentWorkItem; + + /// + /// STP performance counters + /// + private ISTPInstancePerformanceCounters _pcs = NullSTPInstancePerformanceCounters.Instance; + + #endregion + + #region Construction and Finalization + + /// + /// Constructor + /// + public SmartThreadPool() + { + Initialize(); + } + + /// + /// Constructor + /// + /// Idle timeout in milliseconds + public SmartThreadPool(int idleTimeout) + { + _stpStartInfo.IdleTimeout = idleTimeout; + Initialize(); + } + + /// + /// Constructor + /// + /// Idle timeout in milliseconds + /// Upper limit of threads in the pool + public SmartThreadPool( + int idleTimeout, + int maxWorkerThreads) + { + _stpStartInfo.IdleTimeout = idleTimeout; + _stpStartInfo.MaxWorkerThreads = maxWorkerThreads; + Initialize(); + } + + /// + /// Constructor + /// + /// Idle timeout in milliseconds + /// Upper limit of threads in the pool + /// Lower limit of threads in the pool + public SmartThreadPool( + int idleTimeout, + int maxWorkerThreads, + int minWorkerThreads) + { + _stpStartInfo.IdleTimeout = idleTimeout; + _stpStartInfo.MaxWorkerThreads = maxWorkerThreads; + _stpStartInfo.MinWorkerThreads = minWorkerThreads; + Initialize(); + } + + /// + /// Constructor + /// + public SmartThreadPool(STPStartInfo stpStartInfo) + { + _stpStartInfo = new STPStartInfo(stpStartInfo); + Initialize(); + } + + private void Initialize() + { + ValidateSTPStartInfo(); + + if (null != _stpStartInfo.PerformanceCounterInstanceName) + { + try + { + _pcs = new STPInstancePerformanceCounters(_stpStartInfo.PerformanceCounterInstanceName); + } + catch(Exception e) + { + Debug.WriteLine("Unable to create Performance Counters: " + e.ToString()); + _pcs = NullSTPInstancePerformanceCounters.Instance; + } + } + + StartOptimalNumberOfThreads(); + } + + private void StartOptimalNumberOfThreads() + { + int threadsCount = Math.Max(_workItemsQueue.Count, _stpStartInfo.MinWorkerThreads); + threadsCount = Math.Min(threadsCount, _stpStartInfo.MaxWorkerThreads); + StartThreads(threadsCount); + } + + private void ValidateSTPStartInfo() + { + if (_stpStartInfo.MinWorkerThreads < 0) + { + throw new ArgumentOutOfRangeException( + "MinWorkerThreads", "MinWorkerThreads cannot be negative"); + } + + if (_stpStartInfo.MaxWorkerThreads <= 0) + { + throw new ArgumentOutOfRangeException( + "MaxWorkerThreads", "MaxWorkerThreads must be greater than zero"); + } + + if (_stpStartInfo.MinWorkerThreads > _stpStartInfo.MaxWorkerThreads) + { + throw new ArgumentOutOfRangeException( + "MinWorkerThreads, maxWorkerThreads", + "MaxWorkerThreads must be greater or equal to MinWorkerThreads"); + } + } + + private void ValidateCallback(Delegate callback) + { + if(callback.GetInvocationList().Length > 1) + { + throw new NotSupportedException("SmartThreadPool doesn't support delegates chains"); + } + } + + #endregion + + #region Thread Processing + + /// + /// Waits on the queue for a work item, shutdown, or timeout. + /// + /// + /// Returns the WaitingCallback or null in case of timeout or shutdown. + /// + private WorkItem Dequeue() + { + WorkItem workItem = + _workItemsQueue.DequeueWorkItem(_stpStartInfo.IdleTimeout, _shuttingDownEvent); + + return workItem; + } + + /// + /// Put a new work item in the queue + /// + /// A work item to queue + private void Enqueue(WorkItem workItem) + { + Enqueue(workItem, true); + } + + /// + /// Put a new work item in the queue + /// + /// A work item to queue + internal void Enqueue(WorkItem workItem, bool incrementWorkItems) + { + // Make sure the workItem is not null + Debug.Assert(null != workItem); + + if (incrementWorkItems) + { + IncrementWorkItemsCount(); + } + + _workItemsQueue.EnqueueWorkItem(workItem); + workItem.WorkItemIsQueued(); + + // If all the threads are busy then try to create a new one + if ((InUseThreads + WaitingCallbacks) > _workerThreads.Count) + { + StartThreads(1); + } + } + + private void IncrementWorkItemsCount() + { + _pcs.SampleWorkItems(_workItemsQueue.Count, _workItemsProcessed); + + int count = Interlocked.Increment(ref _currentWorkItemsCount); + //Trace.WriteLine("WorkItemsCount = " + _currentWorkItemsCount.ToString()); + if (count == 1) + { + //Trace.WriteLine("STP is NOT idle"); + _isIdleWaitHandle.Reset(); + } + } + + private void DecrementWorkItemsCount() + { + ++_workItemsProcessed; + + // The counter counts even if the work item was cancelled + _pcs.SampleWorkItems(_workItemsQueue.Count, _workItemsProcessed); + + int count = Interlocked.Decrement(ref _currentWorkItemsCount); + //Trace.WriteLine("WorkItemsCount = " + _currentWorkItemsCount.ToString()); + if (count == 0) + { + //Trace.WriteLine("STP is idle"); + _isIdleWaitHandle.Set(); + } + } + + internal void RegisterWorkItemsGroup(IWorkItemsGroup workItemsGroup) + { + _workItemsGroups[workItemsGroup] = workItemsGroup; + } + + internal void UnregisterWorkItemsGroup(IWorkItemsGroup workItemsGroup) + { + if (_workItemsGroups.Contains(workItemsGroup)) + { + _workItemsGroups.Remove(workItemsGroup); + } + } + + /// + /// Inform that the current thread is about to quit or quiting. + /// The same thread may call this method more than once. + /// + private void InformCompleted() + { + // There is no need to lock the two methods together + // since only the current thread removes itself + // and the _workerThreads is a synchronized hashtable + if (_workerThreads.Contains(Thread.CurrentThread)) + { + _workerThreads.Remove(Thread.CurrentThread); + _pcs.SampleThreads(_workerThreads.Count, _inUseWorkerThreads); + } + } + + /// + /// Starts new threads + /// + /// The number of threads to start + private void StartThreads(int threadsCount) + { + if (_stpStartInfo.StartSuspended) + { + return; + } + + lock(_workerThreads.SyncRoot) + { + // Don't start threads on shut down + if (_shutdown) + { + return; + } + + for(int i = 0; i < threadsCount; ++i) + { + // Don't create more threads then the upper limit + if (_workerThreads.Count >= _stpStartInfo.MaxWorkerThreads) + { + return; + } + + // Create a new thread + Thread workerThread = new Thread(new ThreadStart(ProcessQueuedItems)); + + // Configure the new thread and start it + workerThread.Name = "STP " + Name + " Thread #" + _threadCounter; + workerThread.IsBackground = true; + workerThread.Priority = _stpStartInfo.ThreadPriority; + workerThread.Start(); + ++_threadCounter; + + // Add the new thread to the hashtable and update its creation + // time. + _workerThreads[workerThread] = DateTime.Now; + _pcs.SampleThreads(_workerThreads.Count, _inUseWorkerThreads); + } + } + } + + /// + /// A worker thread method that processes work items from the work items queue. + /// + private void ProcessQueuedItems() + { + // Initialize the _smartThreadPool variable + _smartThreadPool = this; + + try + { + bool bInUseWorkerThreadsWasIncremented = false; + + // Process until shutdown. + while(!_shutdown) + { + // Update the last time this thread was seen alive. + // It's good for debugging. + _workerThreads[Thread.CurrentThread] = DateTime.Now; + + // Wait for a work item, shutdown, or timeout + WorkItem workItem = Dequeue(); + + // Update the last time this thread was seen alive. + // It's good for debugging. + _workerThreads[Thread.CurrentThread] = DateTime.Now; + + // On timeout or shut down. + if (null == workItem) + { + // Double lock for quit. + if (_workerThreads.Count > _stpStartInfo.MinWorkerThreads) + { + lock(_workerThreads.SyncRoot) + { + if (_workerThreads.Count > _stpStartInfo.MinWorkerThreads) + { + // Inform that the thread is quiting and then quit. + // This method must be called within this lock or else + // more threads will quit and the thread pool will go + // below the lower limit. + InformCompleted(); + break; + } + } + } + } + + // If we didn't quit then skip to the next iteration. + if (null == workItem) + { + continue; + } + + try + { + // Initialize the value to false + bInUseWorkerThreadsWasIncremented = false; + + // Change the state of the work item to 'in progress' if possible. + // We do it here so if the work item has been canceled we won't + // increment the _inUseWorkerThreads. + // The cancel mechanism doesn't delete items from the queue, + // it marks the work item as canceled, and when the work item + // is dequeued, we just skip it. + // If the post execute of work item is set to always or to + // call when the work item is canceled then the StartingWorkItem() + // will return true, so the post execute can run. + if (!workItem.StartingWorkItem()) + { + continue; + } + + // Execute the callback. Make sure to accurately + // record how many callbacks are currently executing. + int inUseWorkerThreads = Interlocked.Increment(ref _inUseWorkerThreads); + _pcs.SampleThreads(_workerThreads.Count, inUseWorkerThreads); + + // Mark that the _inUseWorkerThreads incremented, so in the finally{} + // statement we will decrement it correctly. + bInUseWorkerThreadsWasIncremented = true; + + // Set the _currentWorkItem to the current work item + _currentWorkItem = workItem; + + ExecuteWorkItem(workItem); + } + catch(Exception ex) + { + ex.GetHashCode(); + // Do nothing + } + finally + { + if (null != workItem) + { + workItem.DisposeOfState(); + } + + // Set the _currentWorkItem to null, since we + // no longer run user's code. + _currentWorkItem = null; + + // Decrement the _inUseWorkerThreads only if we had + // incremented it. Note the cancelled work items don't + // increment _inUseWorkerThreads. + if (bInUseWorkerThreadsWasIncremented) + { + int inUseWorkerThreads = Interlocked.Decrement(ref _inUseWorkerThreads); + _pcs.SampleThreads(_workerThreads.Count, inUseWorkerThreads); + } + + // Notify that the work item has been completed. + // WorkItemsGroup may enqueue their next work item. + workItem.FireWorkItemCompleted(); + + // Decrement the number of work items here so the idle + // ManualResetEvent won't fluctuate. + DecrementWorkItemsCount(); + } + } + } + catch(ThreadAbortException tae) + { + tae.GetHashCode(); + // Handle the abort exception gracfully. + Thread.ResetAbort(); + } + catch(Exception e) + { + Debug.Assert(null != e); + } + finally + { + InformCompleted(); + } + } + + private void ExecuteWorkItem(WorkItem workItem) + { + _pcs.SampleWorkItemsWaitTime(workItem.WaitingTime); + try + { + workItem.Execute(); + } + catch + { + throw; + } + finally + { + _pcs.SampleWorkItemsProcessTime(workItem.ProcessTime); + } + } + + + #endregion + + #region Public Methods + + /// + /// Queue a work item + /// + /// A callback to execute + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemCallback callback) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, callback); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// The priority of the work item + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemCallback callback, WorkItemPriority workItemPriority) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, callback, workItemPriority); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// Work item info + /// A callback to execute + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, workItemInfo, callback); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, callback, state); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// The work item priority + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, WorkItemPriority workItemPriority) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, callback, state, workItemPriority); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// Work item information + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback, object state) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, workItemInfo, callback, state); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// Returns a work item result + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, callback, state, postExecuteWorkItemCallback); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// The work item priority + /// Returns a work item result + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + WorkItemPriority workItemPriority) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, callback, state, postExecuteWorkItemCallback, workItemPriority); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// Indicates on which cases to call to the post execute callback + /// Returns a work item result + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, callback, state, postExecuteWorkItemCallback, callToPostExecute); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// Indicates on which cases to call to the post execute callback + /// The work item priority + /// Returns a work item result + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute, + WorkItemPriority workItemPriority) + { + ValidateNotDisposed(); + ValidateCallback(callback); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _stpStartInfo, callback, state, postExecuteWorkItemCallback, callToPostExecute, workItemPriority); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Wait for the thread pool to be idle + /// + public void WaitForIdle() + { + WaitForIdle(Timeout.Infinite); + } + + /// + /// Wait for the thread pool to be idle + /// + public bool WaitForIdle(TimeSpan timeout) + { + return WaitForIdle((int)timeout.TotalMilliseconds); + } + + /// + /// Wait for the thread pool to be idle + /// + public bool WaitForIdle(int millisecondsTimeout) + { + ValidateWaitForIdle(); + return _isIdleWaitHandle.WaitOne(millisecondsTimeout, false); + } + + private void ValidateWaitForIdle() + { + if(_smartThreadPool == this) + { + throw new NotSupportedException( + "WaitForIdle cannot be called from a thread on its SmartThreadPool, it will cause may cause a deadlock"); + } + } + + internal void ValidateWorkItemsGroupWaitForIdle(IWorkItemsGroup workItemsGroup) + { + ValidateWorkItemsGroupWaitForIdleImpl(workItemsGroup, SmartThreadPool._currentWorkItem); + if ((null != workItemsGroup) && + (null != SmartThreadPool._currentWorkItem) && + SmartThreadPool._currentWorkItem.WasQueuedBy(workItemsGroup)) + { + throw new NotSupportedException("WaitForIdle cannot be called from a thread on its SmartThreadPool, it will cause may cause a deadlock"); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ValidateWorkItemsGroupWaitForIdleImpl(IWorkItemsGroup workItemsGroup, WorkItem workItem) + { + if ((null != workItemsGroup) && + (null != workItem) && + workItem.WasQueuedBy(workItemsGroup)) + { + throw new NotSupportedException("WaitForIdle cannot be called from a thread on its SmartThreadPool, it will cause may cause a deadlock"); + } + } + + + + /// + /// Force the SmartThreadPool to shutdown + /// + public void Shutdown() + { + Shutdown(true, 0); + } + + public void Shutdown(bool forceAbort, TimeSpan timeout) + { + Shutdown(forceAbort, (int)timeout.TotalMilliseconds); + } + + /// + /// Empties the queue of work items and abort the threads in the pool. + /// + public void Shutdown(bool forceAbort, int millisecondsTimeout) + { + ValidateNotDisposed(); + + ISTPInstancePerformanceCounters pcs = _pcs; + + if (NullSTPInstancePerformanceCounters.Instance != _pcs) + { + _pcs.Dispose(); + // Set the _pcs to "null" to stop updating the performance + // counters + _pcs = NullSTPInstancePerformanceCounters.Instance; + } + + Thread [] threads = null; + lock(_workerThreads.SyncRoot) + { + // Shutdown the work items queue + _workItemsQueue.Dispose(); + + // Signal the threads to exit + _shutdown = true; + _shuttingDownEvent.Set(); + + // Make a copy of the threads' references in the pool + threads = new Thread [_workerThreads.Count]; + _workerThreads.Keys.CopyTo(threads, 0); + } + + int millisecondsLeft = millisecondsTimeout; + DateTime start = DateTime.Now; + bool waitInfinitely = (Timeout.Infinite == millisecondsTimeout); + bool timeout = false; + + // Each iteration we update the time left for the timeout. + foreach(Thread thread in threads) + { + // Join don't work with negative numbers + if (!waitInfinitely && (millisecondsLeft < 0)) + { + timeout = true; + break; + } + + // Wait for the thread to terminate + bool success = thread.Join(millisecondsLeft); + if(!success) + { + timeout = true; + break; + } + + if(!waitInfinitely) + { + // Update the time left to wait + TimeSpan ts = DateTime.Now - start; + millisecondsLeft = millisecondsTimeout - (int)ts.TotalMilliseconds; + } + } + + if (timeout && forceAbort) + { + // Abort the threads in the pool + foreach(Thread thread in threads) + { + if ((thread != null) && thread.IsAlive) + { + try + { + thread.Abort("Shutdown"); + } + catch(SecurityException e) + { + e.GetHashCode(); + } + catch(ThreadStateException ex) + { + ex.GetHashCode(); + // In case the thread has been terminated + // after the check if it is alive. + } + } + } + } + + // Dispose of the performance counters + pcs.Dispose(); + } + + /// + /// Wait for all work items to complete + /// + /// Array of work item result objects + /// + /// true when every work item in workItemResults has completed; otherwise false. + /// + public static bool WaitAll( + IWorkItemResult [] workItemResults) + { + return WaitAll(workItemResults, Timeout.Infinite, true); + } + + /// + /// Wait for all work items to complete + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// + /// true when every work item in workItemResults has completed; otherwise false. + /// + public static bool WaitAll( + IWorkItemResult [] workItemResults, + TimeSpan timeout, + bool exitContext) + { + return WaitAll(workItemResults, (int)timeout.TotalMilliseconds, exitContext); + } + + /// + /// Wait for all work items to complete + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// A cancel wait handle to interrupt the wait if needed + /// + /// true when every work item in workItemResults has completed; otherwise false. + /// + public static bool WaitAll( + IWorkItemResult [] workItemResults, + TimeSpan timeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + return WaitAll(workItemResults, (int)timeout.TotalMilliseconds, exitContext, cancelWaitHandle); + } + + /// + /// Wait for all work items to complete + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// + /// true when every work item in workItemResults has completed; otherwise false. + /// + public static bool WaitAll( + IWorkItemResult [] workItemResults, + int millisecondsTimeout, + bool exitContext) + { + return WorkItem.WaitAll(workItemResults, millisecondsTimeout, exitContext, null); + } + + /// + /// Wait for all work items to complete + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// A cancel wait handle to interrupt the wait if needed + /// + /// true when every work item in workItemResults has completed; otherwise false. + /// + public static bool WaitAll( + IWorkItemResult [] workItemResults, + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + return WorkItem.WaitAll(workItemResults, millisecondsTimeout, exitContext, cancelWaitHandle); + } + + + /// + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// + /// Array of work item result objects + /// + /// The array index of the work item result that satisfied the wait, or WaitTimeout if any of the work items has been canceled. + /// + public static int WaitAny( + IWorkItemResult [] workItemResults) + { + return WaitAny(workItemResults, Timeout.Infinite, true); + } + + /// + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// + public static int WaitAny( + IWorkItemResult [] workItemResults, + TimeSpan timeout, + bool exitContext) + { + return WaitAny(workItemResults, (int)timeout.TotalMilliseconds, exitContext); + } + + /// + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// A cancel wait handle to interrupt the wait if needed + /// + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// + public static int WaitAny( + IWorkItemResult [] workItemResults, + TimeSpan timeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + return WaitAny(workItemResults, (int)timeout.TotalMilliseconds, exitContext, cancelWaitHandle); + } + + /// + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// + public static int WaitAny( + IWorkItemResult [] workItemResults, + int millisecondsTimeout, + bool exitContext) + { + return WorkItem.WaitAny(workItemResults, millisecondsTimeout, exitContext, null); + } + + /// + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// A cancel wait handle to interrupt the wait if needed + /// + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// + public static int WaitAny( + IWorkItemResult [] workItemResults, + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + return WorkItem.WaitAny(workItemResults, millisecondsTimeout, exitContext, cancelWaitHandle); + } + + public IWorkItemsGroup CreateWorkItemsGroup(int concurrency) + { + IWorkItemsGroup workItemsGroup = new WorkItemsGroup(this, concurrency, _stpStartInfo); + return workItemsGroup; + } + + public IWorkItemsGroup CreateWorkItemsGroup(int concurrency, WIGStartInfo wigStartInfo) + { + IWorkItemsGroup workItemsGroup = new WorkItemsGroup(this, concurrency, wigStartInfo); + return workItemsGroup; + } + + public event WorkItemsGroupIdleHandler OnIdle + { + add + { + throw new NotImplementedException("This event is not implemented in the SmartThreadPool class. Please create a WorkItemsGroup in order to use this feature."); + //_onIdle += value; + } + remove + { + throw new NotImplementedException("This event is not implemented in the SmartThreadPool class. Please create a WorkItemsGroup in order to use this feature."); + //_onIdle -= value; + } + } + + public void Cancel() + { + ICollection workItemsGroups = _workItemsGroups.Values; + foreach(WorkItemsGroup workItemsGroup in workItemsGroups) + { + workItemsGroup.Cancel(); + } + } + + public void Start() + { + lock (this) + { + if (!this._stpStartInfo.StartSuspended) + { + return; + } + _stpStartInfo.StartSuspended = false; + } + + ICollection workItemsGroups = _workItemsGroups.Values; + foreach(WorkItemsGroup workItemsGroup in workItemsGroups) + { + workItemsGroup.OnSTPIsStarting(); + } + + StartOptimalNumberOfThreads(); + } + + #endregion + + #region Properties + + /// + /// Get/Set the name of the SmartThreadPool instance + /// + public string Name + { + get + { + return _name; + } + + set + { + _name = value; + } + } + + /// + /// Get the lower limit of threads in the pool. + /// + public int MinThreads + { + get + { + ValidateNotDisposed(); + return _stpStartInfo.MinWorkerThreads; + } + } + + /// + /// Get the upper limit of threads in the pool. + /// + public int MaxThreads + { + get + { + ValidateNotDisposed(); + return _stpStartInfo.MaxWorkerThreads; + } + } + /// + /// Get the number of threads in the thread pool. + /// Should be between the lower and the upper limits. + /// + public int ActiveThreads + { + get + { + ValidateNotDisposed(); + return _workerThreads.Count; + } + } + + /// + /// Get the number of busy (not idle) threads in the thread pool. + /// + public int InUseThreads + { + get + { + ValidateNotDisposed(); + return _inUseWorkerThreads; + } + } + + /// + /// Get the number of work items in the queue. + /// + public int WaitingCallbacks + { + get + { + ValidateNotDisposed(); + return _workItemsQueue.Count; + } + } + + + public event EventHandler Idle + { + add + { + _stpIdle += value; + } + + remove + { + _stpIdle -= value; + } + } + + #endregion + + #region IDisposable Members + + ~SmartThreadPool() + { + Dispose(); + } + + public void Dispose() + { + if (!_isDisposed) + { + if (!_shutdown) + { + Shutdown(); + } + + if (null != _shuttingDownEvent) + { + _shuttingDownEvent.Close(); + _shuttingDownEvent = null; + } + _workerThreads.Clear(); + _isDisposed = true; + GC.SuppressFinalize(this); + } + } + + private void ValidateNotDisposed() + { + if(_isDisposed) + { + throw new ObjectDisposedException(GetType().ToString(), "The SmartThreadPool has been shutdown"); + } + } + #endregion + } + #endregion +} diff --git a/SmartThreadPool/SmartThreadPool.csproj b/SmartThreadPool/SmartThreadPool.csproj new file mode 100644 index 0000000..1f25584 --- /dev/null +++ b/SmartThreadPool/SmartThreadPool.csproj @@ -0,0 +1,141 @@ + + + Local + 8.0.50727 + 2.0 + {8684FC56-A679-4E2E-8F96-E172FB062EB6} + Debug + AnyCPU + + + + + SmartThreadPool + + + JScript + Grid + IE50 + false + Library + SmartThreadPool + OnBuildSuccess + + + + + + + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + System + + + System.Data + + + System.Web + + + System.XML + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + \ No newline at end of file diff --git a/SmartThreadPool/WIGStartInfo.cs b/SmartThreadPool/WIGStartInfo.cs new file mode 100644 index 0000000..5f73a9a --- /dev/null +++ b/SmartThreadPool/WIGStartInfo.cs @@ -0,0 +1,99 @@ +// Ami Bar +// amibar@gmail.com + +namespace Amib.Threading +{ + /// + /// Summary description for WIGStartInfo. + /// + public class WIGStartInfo + { + /// + /// Use the caller's security context + /// + private bool _useCallerCallContext; + + /// + /// Use the caller's HTTP context + /// + private bool _useCallerHttpContext; + + /// + /// Dispose of the state object of a work item + /// + private bool _disposeOfStateObjects; + + /// + /// The option to run the post execute + /// + private CallToPostExecute _callToPostExecute; + + /// + /// A post execute callback to call when none is provided in + /// the QueueWorkItem method. + /// + private PostExecuteWorkItemCallback _postExecuteWorkItemCallback; + + /// + /// Indicate the WorkItemsGroup to suspend the handling of the work items + /// until the Start() method is called. + /// + private bool _startSuspended; + + public WIGStartInfo() + { + _useCallerCallContext = SmartThreadPool.DefaultUseCallerCallContext; + _useCallerHttpContext = SmartThreadPool.DefaultUseCallerHttpContext; + _disposeOfStateObjects = SmartThreadPool.DefaultDisposeOfStateObjects; + _callToPostExecute = SmartThreadPool.DefaultCallToPostExecute; + _postExecuteWorkItemCallback = SmartThreadPool.DefaultPostExecuteWorkItemCallback; + _startSuspended = SmartThreadPool.DefaultStartSuspended; + } + + public WIGStartInfo(WIGStartInfo wigStartInfo) + { + _useCallerCallContext = wigStartInfo._useCallerCallContext; + _useCallerHttpContext = wigStartInfo._useCallerHttpContext; + _disposeOfStateObjects = wigStartInfo._disposeOfStateObjects; + _callToPostExecute = wigStartInfo._callToPostExecute; + _postExecuteWorkItemCallback = wigStartInfo._postExecuteWorkItemCallback; + _startSuspended = wigStartInfo._startSuspended; + } + + public bool UseCallerCallContext + { + get { return _useCallerCallContext; } + set { _useCallerCallContext = value; } + } + + public bool UseCallerHttpContext + { + get { return _useCallerHttpContext; } + set { _useCallerHttpContext = value; } + } + + public bool DisposeOfStateObjects + { + get { return _disposeOfStateObjects; } + set { _disposeOfStateObjects = value; } + } + + public CallToPostExecute CallToPostExecute + { + get { return _callToPostExecute; } + set { _callToPostExecute = value; } + } + + public PostExecuteWorkItemCallback PostExecuteWorkItemCallback + { + get { return _postExecuteWorkItemCallback; } + set { _postExecuteWorkItemCallback = value; } + } + + public bool StartSuspended + { + get { return _startSuspended; } + set { _startSuspended = value; } + } + } +} diff --git a/SmartThreadPool/WorkItem.cs b/SmartThreadPool/WorkItem.cs new file mode 100644 index 0000000..eb06ca8 --- /dev/null +++ b/SmartThreadPool/WorkItem.cs @@ -0,0 +1,1019 @@ +// Ami Bar +// amibar@gmail.com + +using System; +using System.Threading; +using System.Diagnostics; + +namespace Amib.Threading.Internal +{ + #region WorkItem Delegate + + /// + /// An internal delegate to call when the WorkItem starts or completes + /// + internal delegate void WorkItemStateCallback(WorkItem workItem); + + #endregion + + #region IInternalWorkItemResult interface + + public class CanceledWorkItemsGroup + { + public readonly static CanceledWorkItemsGroup NotCanceledWorkItemsGroup = new CanceledWorkItemsGroup(); + + private bool _isCanceled = false; + public bool IsCanceled + { + get { return _isCanceled; } + set { _isCanceled = value; } + } + } + + internal interface IInternalWorkItemResult + { + event WorkItemStateCallback OnWorkItemStarted; + event WorkItemStateCallback OnWorkItemCompleted; + } + + #endregion + + #region IWorkItem interface + + public interface IWorkItem + { + + } + + #endregion + + #region WorkItem class + + /// + /// Holds a callback delegate and the state for that delegate. + /// + public class WorkItem : IHasWorkItemPriority, IWorkItem + { + #region WorkItemState enum + + /// + /// Indicates the state of the work item in the thread pool + /// + private enum WorkItemState + { + InQueue, + InProgress, + Completed, + Canceled, + } + + #endregion + + #region Member Variables + + /// + /// Callback delegate for the callback. + /// + private WorkItemCallback _callback; + + /// + /// State with which to call the callback delegate. + /// + private object _state; + + /// + /// Stores the caller's context + /// + private CallerThreadContext _callerContext; + + /// + /// Holds the result of the mehtod + /// + private object _result; + + /// + /// Hold the exception if the method threw it + /// + private Exception _exception; + + /// + /// Hold the state of the work item + /// + private WorkItemState _workItemState; + + /// + /// A ManualResetEvent to indicate that the result is ready + /// + private ManualResetEvent _workItemCompleted; + + /// + /// A reference count to the _workItemCompleted. + /// When it reaches to zero _workItemCompleted is Closed + /// + private int _workItemCompletedRefCount; + + /// + /// Represents the result state of the work item + /// + private WorkItemResult _workItemResult; + + /// + /// Work item info + /// + private WorkItemInfo _workItemInfo; + + /// + /// Called when the WorkItem starts + /// + private event WorkItemStateCallback _workItemStartedEvent; + + /// + /// Called when the WorkItem completes + /// + private event WorkItemStateCallback _workItemCompletedEvent; + + /// + /// A reference to an object that indicates whatever the + /// WorkItemsGroup has been canceled + /// + private CanceledWorkItemsGroup _canceledWorkItemsGroup = CanceledWorkItemsGroup.NotCanceledWorkItemsGroup; + + /// + /// The work item group this work item belong to. + /// + /// + private IWorkItemsGroup _workItemsGroup; + + #region Performance Counter fields + + /// + /// The time when the work items is queued. + /// Used with the performance counter. + /// + private DateTime _queuedTime; + + /// + /// The time when the work items starts its execution. + /// Used with the performance counter. + /// + private DateTime _beginProcessTime; + + /// + /// The time when the work items ends its execution. + /// Used with the performance counter. + /// + private DateTime _endProcessTime; + + #endregion + + #endregion + + #region Properties + + public TimeSpan WaitingTime + { + get + { + return (_beginProcessTime - _queuedTime); + } + } + + public TimeSpan ProcessTime + { + get + { + return (_endProcessTime - _beginProcessTime); + } + } + + #endregion + + #region Construction + + /// + /// Initialize the callback holding object. + /// + /// Callback delegate for the callback. + /// State with which to call the callback delegate. + /// + /// We assume that the WorkItem object is created within the thread + /// that meant to run the callback + public WorkItem( + IWorkItemsGroup workItemsGroup, + WorkItemInfo workItemInfo, + WorkItemCallback callback, + object state) + { + _workItemsGroup = workItemsGroup; + _workItemInfo = workItemInfo; + + if (_workItemInfo.UseCallerCallContext || _workItemInfo.UseCallerHttpContext) + { + _callerContext = CallerThreadContext.Capture(_workItemInfo.UseCallerCallContext, _workItemInfo.UseCallerHttpContext); + } + + _callback = callback; + _state = state; + _workItemResult = new WorkItemResult(this); + Initialize(); + } + + internal void Initialize() + { + _workItemState = WorkItemState.InQueue; + _workItemCompleted = null; + _workItemCompletedRefCount = 0; + } + + internal bool WasQueuedBy(IWorkItemsGroup workItemsGroup) + { + return (workItemsGroup == _workItemsGroup); + } + + + #endregion + + #region Methods + + public CanceledWorkItemsGroup CanceledWorkItemsGroup + { + get + { + return _canceledWorkItemsGroup; + } + + set + { + _canceledWorkItemsGroup = value; + } + } + + /// + /// Change the state of the work item to in progress if it wasn't canceled. + /// + /// + /// Return true on success or false in case the work item was canceled. + /// If the work item needs to run a post execute then the method will return true. + /// + public bool StartingWorkItem() + { + _beginProcessTime = DateTime.Now; + + lock(this) + { + if (IsCanceled) + { + bool result = false; + if ((_workItemInfo.PostExecuteWorkItemCallback != null) && + ((_workItemInfo.CallToPostExecute & CallToPostExecute.WhenWorkItemCanceled) == CallToPostExecute.WhenWorkItemCanceled)) + { + result = true; + } + + return result; + } + + Debug.Assert(WorkItemState.InQueue == GetWorkItemState()); + + SetWorkItemState(WorkItemState.InProgress); + } + + return true; + } + + /// + /// Execute the work item and the post execute + /// + public void Execute() + { + CallToPostExecute currentCallToPostExecute = 0; + + // Execute the work item if we are in the correct state + switch(GetWorkItemState()) + { + case WorkItemState.InProgress: + currentCallToPostExecute |= CallToPostExecute.WhenWorkItemNotCanceled; + ExecuteWorkItem(); + break; + case WorkItemState.Canceled: + currentCallToPostExecute |= CallToPostExecute.WhenWorkItemCanceled; + break; + default: + Debug.Assert(false); + throw new NotSupportedException(); + } + + // Run the post execute as needed + if ((currentCallToPostExecute & _workItemInfo.CallToPostExecute) != 0) + { + PostExecute(); + } + + _endProcessTime = DateTime.Now; + } + + internal void FireWorkItemCompleted() + { + try + { + if (null != _workItemCompletedEvent) + { + _workItemCompletedEvent(this); + } + } + catch // Ignore exceptions + {} + } + + /// + /// Execute the work item + /// + private void ExecuteWorkItem() + { + CallerThreadContext ctc = null; + if (null != _callerContext) + { + ctc = CallerThreadContext.Capture(_callerContext.CapturedCallContext, _callerContext.CapturedHttpContext); + CallerThreadContext.Apply(_callerContext); + } + + Exception exception = null; + object result = null; + + try + { + result = _callback(_state); + } + catch (Exception e) + { + // Save the exception so we can rethrow it later + exception = e; + } + + if (null != _callerContext) + { + CallerThreadContext.Apply(ctc); + } + + SetResult(result, exception); + } + + /// + /// Runs the post execute callback + /// + private void PostExecute() + { + if (null != _workItemInfo.PostExecuteWorkItemCallback) + { + try + { + _workItemInfo.PostExecuteWorkItemCallback(this._workItemResult); + } + catch (Exception e) + { + Debug.Assert(null != e); + } + } + } + + /// + /// Set the result of the work item to return + /// + /// The result of the work item + internal void SetResult(object result, Exception exception) + { + _result = result; + _exception = exception; + SignalComplete(false); + } + + /// + /// Returns the work item result + /// + /// The work item result + internal IWorkItemResult GetWorkItemResult() + { + return _workItemResult; + } + + /// + /// Wait for all work items to complete + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// A cancel wait handle to interrupt the wait if needed + /// + /// true when every work item in workItemResults has completed; otherwise false. + /// + internal static bool WaitAll( + IWorkItemResult [] workItemResults, + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + if (0 == workItemResults.Length) + { + return true; + } + + bool success; + WaitHandle [] waitHandles = new WaitHandle[workItemResults.Length];; + GetWaitHandles(workItemResults, waitHandles); + + if ((null == cancelWaitHandle) && (waitHandles.Length <= 64)) + { + success = WaitHandle.WaitAll(waitHandles, millisecondsTimeout, exitContext); + } + else + { + success = true; + int millisecondsLeft = millisecondsTimeout; + DateTime start = DateTime.Now; + + WaitHandle [] whs; + if (null != cancelWaitHandle) + { + whs = new WaitHandle [] { null, cancelWaitHandle }; + } + else + { + whs = new WaitHandle [] { null }; + } + + bool waitInfinitely = (Timeout.Infinite == millisecondsTimeout); + // Iterate over the wait handles and wait for each one to complete. + // We cannot use WaitHandle.WaitAll directly, because the cancelWaitHandle + // won't affect it. + // Each iteration we update the time left for the timeout. + for(int i = 0; i < workItemResults.Length; ++i) + { + // WaitAny don't work with negative numbers + if (!waitInfinitely && (millisecondsLeft < 0)) + { + success = false; + break; + } + + whs[0] = waitHandles[i]; + int result = WaitHandle.WaitAny(whs, millisecondsLeft, exitContext); + if((result > 0) || (WaitHandle.WaitTimeout == result)) + { + success = false; + break; + } + + if(!waitInfinitely) + { + // Update the time left to wait + TimeSpan ts = DateTime.Now - start; + millisecondsLeft = millisecondsTimeout - (int)ts.TotalMilliseconds; + } + } + } + // Release the wait handles + ReleaseWaitHandles(workItemResults); + + return success; + } + + /// + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// + /// Array of work item result objects + /// The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely. + /// + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// + /// A cancel wait handle to interrupt the wait if needed + /// + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// + internal static int WaitAny( + IWorkItemResult [] workItemResults, + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + WaitHandle [] waitHandles = null; + + if (null != cancelWaitHandle) + { + waitHandles = new WaitHandle[workItemResults.Length+1]; + GetWaitHandles(workItemResults, waitHandles); + waitHandles[workItemResults.Length] = cancelWaitHandle; + } + else + { + waitHandles = new WaitHandle[workItemResults.Length]; + GetWaitHandles(workItemResults, waitHandles); + } + + int result = WaitHandle.WaitAny(waitHandles, millisecondsTimeout, exitContext); + + // Treat cancel as timeout + if (null != cancelWaitHandle) + { + if (result == workItemResults.Length) + { + result = WaitHandle.WaitTimeout; + } + } + + ReleaseWaitHandles(workItemResults); + + return result; + } + + /// + /// Fill an array of wait handles with the work items wait handles. + /// + /// An array of work item results + /// An array of wait handles to fill + private static void GetWaitHandles( + IWorkItemResult [] workItemResults, + WaitHandle [] waitHandles) + { + for(int i = 0; i < workItemResults.Length; ++i) + { + WorkItemResult wir = workItemResults[i] as WorkItemResult; + Debug.Assert(null != wir, "All workItemResults must be WorkItemResult objects"); + + waitHandles[i] = wir.GetWorkItem().GetWaitHandle(); + } + } + + /// + /// Release the work items' wait handles + /// + /// An array of work item results + private static void ReleaseWaitHandles(IWorkItemResult [] workItemResults) + { + for(int i = 0; i < workItemResults.Length; ++i) + { + WorkItemResult wir = workItemResults[i] as WorkItemResult; + + wir.GetWorkItem().ReleaseWaitHandle(); + } + } + + + #endregion + + #region Private Members + + private WorkItemState GetWorkItemState() + { + if (_canceledWorkItemsGroup.IsCanceled) + { + return WorkItemState.Canceled; + } + return _workItemState; + + } + /// + /// Sets the work item's state + /// + /// The state to set the work item to + private void SetWorkItemState(WorkItemState workItemState) + { + lock(this) + { + _workItemState = workItemState; + } + } + + /// + /// Signals that work item has been completed or canceled + /// + /// Indicates that the work item has been canceled + private void SignalComplete(bool canceled) + { + SetWorkItemState(canceled ? WorkItemState.Canceled : WorkItemState.Completed); + lock(this) + { + // If someone is waiting then signal. + if (null != _workItemCompleted) + { + _workItemCompleted.Set(); + } + } + } + + internal void WorkItemIsQueued() + { + _queuedTime = DateTime.Now; + } + + #endregion + + #region Members exposed by WorkItemResult + + /// + /// Cancel the work item if it didn't start running yet. + /// + /// Returns true on success or false if the work item is in progress or already completed + private bool Cancel() + { + lock(this) + { + switch(GetWorkItemState()) + { + case WorkItemState.Canceled: + //Debug.WriteLine("Work item already canceled"); + return true; + case WorkItemState.Completed: + case WorkItemState.InProgress: + //Debug.WriteLine("Work item cannot be canceled"); + return false; + case WorkItemState.InQueue: + // Signal to the wait for completion that the work + // item has been completed (canceled). There is no + // reason to wait for it to get out of the queue + SignalComplete(true); + //Debug.WriteLine("Work item canceled"); + return true; + } + } + return false; + } + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits for the result, timeout, or cancel. + /// In case of error the method throws and exception + /// + /// The result of the work item + private object GetResult( + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + Exception e = null; + object result = GetResult(millisecondsTimeout, exitContext, cancelWaitHandle, out e); + if (null != e) + { + throw new WorkItemResultException("The work item caused an excpetion, see the inner exception for details", e); + } + return result; + } + + /// + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits for the result, timeout, or cancel. + /// In case of error the e argument is filled with the exception + /// + /// The result of the work item + private object GetResult( + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle, + out Exception e) + { + e = null; + + // Check for cancel + if (WorkItemState.Canceled == GetWorkItemState()) + { + throw new WorkItemCancelException("Work item canceled"); + } + + // Check for completion + if (IsCompleted) + { + e = _exception; + return _result; + } + + // If no cancelWaitHandle is provided + if (null == cancelWaitHandle) + { + WaitHandle wh = GetWaitHandle(); + + bool timeout = !wh.WaitOne(millisecondsTimeout, exitContext); + + ReleaseWaitHandle(); + + if (timeout) + { + throw new WorkItemTimeoutException("Work item timeout"); + } + } + else + { + WaitHandle wh = GetWaitHandle(); + int result = WaitHandle.WaitAny(new WaitHandle[] { wh, cancelWaitHandle }); + ReleaseWaitHandle(); + + switch(result) + { + case 0: + // The work item signaled + // Note that the signal could be also as a result of canceling the + // work item (not the get result) + break; + case 1: + case WaitHandle.WaitTimeout: + throw new WorkItemTimeoutException("Work item timeout"); + default: + Debug.Assert(false); + break; + + } + } + + // Check for cancel + if (WorkItemState.Canceled == GetWorkItemState()) + { + throw new WorkItemCancelException("Work item canceled"); + } + + Debug.Assert(IsCompleted); + + e = _exception; + + // Return the result + return _result; + } + + /// + /// A wait handle to wait for completion, cancel, or timeout + /// + private WaitHandle GetWaitHandle() + { + lock(this) + { + if (null == _workItemCompleted) + { + _workItemCompleted = new ManualResetEvent(IsCompleted); + } + ++_workItemCompletedRefCount; + } + return _workItemCompleted; + } + + private void ReleaseWaitHandle() + { + lock(this) + { + if (null != _workItemCompleted) + { + --_workItemCompletedRefCount; + if (0 == _workItemCompletedRefCount) + { + _workItemCompleted.Close(); + _workItemCompleted = null; + } + } + } + } + + /// + /// Returns true when the work item has completed or canceled + /// + private bool IsCompleted + { + get + { + lock(this) + { + WorkItemState workItemState = GetWorkItemState(); + return ((workItemState == WorkItemState.Completed) || + (workItemState == WorkItemState.Canceled)); + } + } + } + + /// + /// Returns true when the work item has canceled + /// + public bool IsCanceled + { + get + { + lock(this) + { + return (GetWorkItemState() == WorkItemState.Canceled); + } + } + } + + #endregion + + #region IHasWorkItemPriority Members + + /// + /// Returns the priority of the work item + /// + public WorkItemPriority WorkItemPriority + { + get + { + return _workItemInfo.WorkItemPriority; + } + } + + #endregion + + internal event WorkItemStateCallback OnWorkItemStarted + { + add + { + _workItemStartedEvent += value; + } + remove + { + _workItemStartedEvent -= value; + } + } + + internal event WorkItemStateCallback OnWorkItemCompleted + { + add + { + _workItemCompletedEvent += value; + } + remove + { + _workItemCompletedEvent -= value; + } + } + + + #region WorkItemResult class + + private class WorkItemResult : IWorkItemResult, IInternalWorkItemResult + { + /// + /// A back reference to the work item + /// + private WorkItem _workItem; + + public WorkItemResult(WorkItem workItem) + { + _workItem = workItem; + } + + internal WorkItem GetWorkItem() + { + return _workItem; + } + + #region IWorkItemResult Members + + public bool IsCompleted + { + get + { + return _workItem.IsCompleted; + } + } + + public bool IsCanceled + { + get + { + return _workItem.IsCanceled; + } + } + + public object GetResult() + { + return _workItem.GetResult(Timeout.Infinite, true, null); + } + + public object GetResult(int millisecondsTimeout, bool exitContext) + { + return _workItem.GetResult(millisecondsTimeout, exitContext, null); + } + + public object GetResult(TimeSpan timeout, bool exitContext) + { + return _workItem.GetResult((int)timeout.TotalMilliseconds, exitContext, null); + } + + public object GetResult(int millisecondsTimeout, bool exitContext, WaitHandle cancelWaitHandle) + { + return _workItem.GetResult(millisecondsTimeout, exitContext, cancelWaitHandle); + } + + public object GetResult(TimeSpan timeout, bool exitContext, WaitHandle cancelWaitHandle) + { + return _workItem.GetResult((int)timeout.TotalMilliseconds, exitContext, cancelWaitHandle); + } + + public object GetResult(out Exception e) + { + return _workItem.GetResult(Timeout.Infinite, true, null, out e); + } + + public object GetResult(int millisecondsTimeout, bool exitContext, out Exception e) + { + return _workItem.GetResult(millisecondsTimeout, exitContext, null, out e); + } + + public object GetResult(TimeSpan timeout, bool exitContext, out Exception e) + { + return _workItem.GetResult((int)timeout.TotalMilliseconds, exitContext, null, out e); + } + + public object GetResult(int millisecondsTimeout, bool exitContext, WaitHandle cancelWaitHandle, out Exception e) + { + return _workItem.GetResult(millisecondsTimeout, exitContext, cancelWaitHandle, out e); + } + + public object GetResult(TimeSpan timeout, bool exitContext, WaitHandle cancelWaitHandle, out Exception e) + { + return _workItem.GetResult((int)timeout.TotalMilliseconds, exitContext, cancelWaitHandle, out e); + } + + public bool Cancel() + { + return _workItem.Cancel(); + } + + public object State + { + get + { + return _workItem._state; + } + } + + public WorkItemPriority WorkItemPriority + { + get + { + return _workItem._workItemInfo.WorkItemPriority; + } + } + + /// + /// Return the result, same as GetResult() + /// + public object Result + { + get { return GetResult(); } + } + + /// + /// Returns the exception if occured otherwise returns null. + /// This value is valid only after the work item completed, + /// before that it is always null. + /// + public object Exception + { + get { return _workItem._exception; } + } + + #endregion + + #region IInternalWorkItemResult Members + + public event WorkItemStateCallback OnWorkItemStarted + { + add + { + _workItem.OnWorkItemStarted += value; + } + remove + { + _workItem.OnWorkItemStarted -= value; + } + } + + + public event WorkItemStateCallback OnWorkItemCompleted + { + add + { + _workItem.OnWorkItemCompleted += value; + } + remove + { + _workItem.OnWorkItemCompleted -= value; + } + } + + #endregion + } + + #endregion + + public void DisposeOfState() + { + if (_workItemInfo.DisposeOfStateObjects) + { + IDisposable disp = _state as IDisposable; + if (null != disp) + { + disp.Dispose(); + _state = null; + } + } + } + } + #endregion +} diff --git a/SmartThreadPool/WorkItemFactory.cs b/SmartThreadPool/WorkItemFactory.cs new file mode 100644 index 0000000..1f96149 --- /dev/null +++ b/SmartThreadPool/WorkItemFactory.cs @@ -0,0 +1,333 @@ +// Ami Bar +// amibar@gmail.com + +using System; + +namespace Amib.Threading.Internal +{ + #region WorkItemFactory class + + public class WorkItemFactory + { + /// + /// Create a new work item + /// + /// Work item group start information + /// A callback to execute + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback) + { + return CreateWorkItem(workItemsGroup, wigStartInfo, callback, null); + } + + /// + /// Create a new work item + /// + /// Work item group start information + /// A callback to execute + /// The priority of the work item + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + WorkItemPriority workItemPriority) + { + return CreateWorkItem(workItemsGroup, wigStartInfo, callback, null, workItemPriority); + } + + /// + /// Create a new work item + /// + /// Work item group start information + /// Work item info + /// A callback to execute + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemInfo workItemInfo, + WorkItemCallback callback) + { + return CreateWorkItem( + workItemsGroup, + wigStartInfo, + workItemInfo, + callback, + null); + } + + /// + /// Create a new work item + /// + /// Work item group start information + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state) + { + ValidateCallback(callback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = wigStartInfo.PostExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = wigStartInfo.CallToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + return workItem; + } + + /// + /// Create a new work item + /// + /// Work item group start information + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// The work item priority + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + WorkItemPriority workItemPriority) + { + ValidateCallback(callback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = wigStartInfo.PostExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = wigStartInfo.CallToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + workItemInfo.WorkItemPriority = workItemPriority; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + /// + /// Create a new work item + /// + /// Work item group start information + /// Work item information + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemInfo workItemInfo, + WorkItemCallback callback, + object state) + { + ValidateCallback(callback); + ValidateCallback(workItemInfo.PostExecuteWorkItemCallback); + + WorkItem workItem = new WorkItem( + workItemsGroup, + new WorkItemInfo(workItemInfo), + callback, + state); + + return workItem; + } + + /// + /// Create a new work item + /// + /// Work item group start information + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback) + { + ValidateCallback(callback); + ValidateCallback(postExecuteWorkItemCallback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = postExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = wigStartInfo.CallToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + /// + /// Create a new work item + /// + /// Work item group start information + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// The work item priority + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + WorkItemPriority workItemPriority) + { + ValidateCallback(callback); + ValidateCallback(postExecuteWorkItemCallback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = postExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = wigStartInfo.CallToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + workItemInfo.WorkItemPriority = workItemPriority; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + /// + /// Create a new work item + /// + /// Work item group start information + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// Indicates on which cases to call to the post execute callback + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute) + { + ValidateCallback(callback); + ValidateCallback(postExecuteWorkItemCallback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = postExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = callToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + /// + /// Create a new work item + /// + /// Work item group start information + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// Indicates on which cases to call to the post execute callback + /// The work item priority + /// Returns a work item + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute, + WorkItemPriority workItemPriority) + { + + ValidateCallback(callback); + ValidateCallback(postExecuteWorkItemCallback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = postExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = callToPostExecute; + workItemInfo.WorkItemPriority = workItemPriority; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + private static void ValidateCallback(Delegate callback) + { + if(callback.GetInvocationList().Length > 1) + { + throw new NotSupportedException("SmartThreadPool doesn't support delegates chains"); + } + } + } + + #endregion +} diff --git a/SmartThreadPool/WorkItemInfo.cs b/SmartThreadPool/WorkItemInfo.cs new file mode 100644 index 0000000..b07c3f0 --- /dev/null +++ b/SmartThreadPool/WorkItemInfo.cs @@ -0,0 +1,102 @@ +// Ami Bar +// amibar@gmail.com + +namespace Amib.Threading +{ + #region WorkItemInfo class + + /// + /// Summary description for WorkItemInfo. + /// + public class WorkItemInfo + { + /// + /// Use the caller's security context + /// + private bool _useCallerCallContext; + + /// + /// Use the caller's security context + /// + private bool _useCallerHttpContext; + + /// + /// Dispose of the state object of a work item + /// + private bool _disposeOfStateObjects; + + /// + /// The option to run the post execute + /// + private CallToPostExecute _callToPostExecute; + + /// + /// A post execute callback to call when none is provided in + /// the QueueWorkItem method. + /// + private PostExecuteWorkItemCallback _postExecuteWorkItemCallback; + + /// + /// The priority of the work item + /// + private WorkItemPriority _workItemPriority; + + public WorkItemInfo() + { + _useCallerCallContext = SmartThreadPool.DefaultUseCallerCallContext; + _useCallerHttpContext = SmartThreadPool.DefaultUseCallerHttpContext; + _disposeOfStateObjects = SmartThreadPool.DefaultDisposeOfStateObjects; + _callToPostExecute = SmartThreadPool.DefaultCallToPostExecute; + _postExecuteWorkItemCallback = SmartThreadPool.DefaultPostExecuteWorkItemCallback; + _workItemPriority = SmartThreadPool.DefaultWorkItemPriority; + } + + public WorkItemInfo(WorkItemInfo workItemInfo) + { + _useCallerCallContext = workItemInfo._useCallerCallContext; + _useCallerHttpContext = workItemInfo._useCallerHttpContext; + _disposeOfStateObjects = workItemInfo._disposeOfStateObjects; + _callToPostExecute = workItemInfo._callToPostExecute; + _postExecuteWorkItemCallback = workItemInfo._postExecuteWorkItemCallback; + _workItemPriority = workItemInfo._workItemPriority; + } + + public bool UseCallerCallContext + { + get { return _useCallerCallContext; } + set { _useCallerCallContext = value; } + } + + public bool UseCallerHttpContext + { + get { return _useCallerHttpContext; } + set { _useCallerHttpContext = value; } + } + + public bool DisposeOfStateObjects + { + get { return _disposeOfStateObjects; } + set { _disposeOfStateObjects = value; } + } + + public CallToPostExecute CallToPostExecute + { + get { return _callToPostExecute; } + set { _callToPostExecute = value; } + } + + public PostExecuteWorkItemCallback PostExecuteWorkItemCallback + { + get { return _postExecuteWorkItemCallback; } + set { _postExecuteWorkItemCallback = value; } + } + + public WorkItemPriority WorkItemPriority + { + get { return _workItemPriority; } + set { _workItemPriority = value; } + } + } + + #endregion +} diff --git a/SmartThreadPool/WorkItemsGroup.cs b/SmartThreadPool/WorkItemsGroup.cs new file mode 100644 index 0000000..c618199 --- /dev/null +++ b/SmartThreadPool/WorkItemsGroup.cs @@ -0,0 +1,512 @@ +// Ami Bar +// amibar@gmail.com + +using System; +using System.Threading; +using System.Runtime.CompilerServices; +using System.Diagnostics; + +namespace Amib.Threading.Internal +{ + #region WorkItemsGroup class + + /// + /// Summary description for WorkItemsGroup. + /// + public class WorkItemsGroup : IWorkItemsGroup + { + #region Private members + + private object _lock = new object(); + /// + /// Contains the name of this instance of SmartThreadPool. + /// Can be changed by the user. + /// + private string _name = "WorkItemsGroup"; + + /// + /// A reference to the SmartThreadPool instance that created this + /// WorkItemsGroup. + /// + private SmartThreadPool _stp; + + /// + /// The OnIdle event + /// + private event WorkItemsGroupIdleHandler _onIdle; + + /// + /// Defines how many work items of this WorkItemsGroup can run at once. + /// + private int _concurrency; + + /// + /// Priority queue to hold work items before they are passed + /// to the SmartThreadPool. + /// + private PriorityQueue _workItemsQueue; + + /// + /// Indicate how many work items are waiting in the SmartThreadPool + /// queue. + /// This value is used to apply the concurrency. + /// + private int _workItemsInStpQueue; + + /// + /// Indicate how many work items are currently running in the SmartThreadPool. + /// This value is used with the Cancel, to calculate if we can send new + /// work items to the STP. + /// + private int _workItemsExecutingInStp = 0; + + /// + /// WorkItemsGroup start information + /// + private WIGStartInfo _workItemsGroupStartInfo; + + /// + /// Signaled when all of the WorkItemsGroup's work item completed. + /// + private ManualResetEvent _isIdleWaitHandle = new ManualResetEvent(true); + + /// + /// A common object for all the work items that this work items group + /// generate so we can mark them to cancel in O(1) + /// + private CanceledWorkItemsGroup _canceledWorkItemsGroup = new CanceledWorkItemsGroup(); + + #endregion + + #region Construction + + public WorkItemsGroup( + SmartThreadPool stp, + int concurrency, + WIGStartInfo wigStartInfo) + { + if (concurrency <= 0) + { + throw new ArgumentOutOfRangeException("concurrency", concurrency, "concurrency must be greater than zero"); + } + _stp = stp; + _concurrency = concurrency; + _workItemsGroupStartInfo = new WIGStartInfo(wigStartInfo); + _workItemsQueue = new PriorityQueue(); + + // The _workItemsInStpQueue gets the number of currently executing work items, + // because once a work item is executing, it cannot be cancelled. + _workItemsInStpQueue = _workItemsExecutingInStp; + } + + #endregion + + #region IWorkItemsGroup implementation + + /// + /// Get/Set the name of the SmartThreadPool instance + /// + public string Name + { + get + { + return _name; + } + + set + { + _name = value; + } + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemCallback callback) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, callback); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// The priority of the work item + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemCallback callback, WorkItemPriority workItemPriority) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, callback, workItemPriority); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// Work item info + /// A callback to execute + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, workItemInfo, callback); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, callback, state); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// The work item priority + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, WorkItemPriority workItemPriority) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, callback, state, workItemPriority); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// Work item information + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// Returns a work item result + public IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback, object state) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, workItemInfo, callback, state); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// Returns a work item result + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, callback, state, postExecuteWorkItemCallback); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// The work item priority + /// Returns a work item result + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + WorkItemPriority workItemPriority) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, callback, state, postExecuteWorkItemCallback, workItemPriority); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// Indicates on which cases to call to the post execute callback + /// Returns a work item result + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, callback, state, postExecuteWorkItemCallback, callToPostExecute); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Queue a work item + /// + /// A callback to execute + /// + /// The context object of the work item. Used for passing arguments to the work item. + /// + /// + /// A delegate to call after the callback completion + /// + /// Indicates on which cases to call to the post execute callback + /// The work item priority + /// Returns a work item result + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute, + WorkItemPriority workItemPriority) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, _workItemsGroupStartInfo, callback, state, postExecuteWorkItemCallback, callToPostExecute, workItemPriority); + EnqueueToSTPNextWorkItem(workItem); + return workItem.GetWorkItemResult(); + } + + /// + /// Wait for the thread pool to be idle + /// + public void WaitForIdle() + { + WaitForIdle(Timeout.Infinite); + } + + /// + /// Wait for the thread pool to be idle + /// + public bool WaitForIdle(TimeSpan timeout) + { + return WaitForIdle((int)timeout.TotalMilliseconds); + } + + /// + /// Wait for the thread pool to be idle + /// + public bool WaitForIdle(int millisecondsTimeout) + { + _stp.ValidateWorkItemsGroupWaitForIdle(this); + return _isIdleWaitHandle.WaitOne(millisecondsTimeout, false); + } + + public int WaitingCallbacks + { + get + { + return _workItemsQueue.Count; + } + } + + public event WorkItemsGroupIdleHandler OnIdle + { + add + { + _onIdle += value; + } + remove + { + _onIdle -= value; + } + } + + public void Cancel() + { + lock(_lock) + { + _canceledWorkItemsGroup.IsCanceled = true; + _workItemsQueue.Clear(); + _workItemsInStpQueue = 0; + _canceledWorkItemsGroup = new CanceledWorkItemsGroup(); + } + } + + public void Start() + { + lock (this) + { + if (!_workItemsGroupStartInfo.StartSuspended) + { + return; + } + _workItemsGroupStartInfo.StartSuspended = false; + } + + for(int i = 0; i < _concurrency; ++i) + { + EnqueueToSTPNextWorkItem(null, false); + } + } + + #endregion + + #region Private methods + + private void RegisterToWorkItemCompletion(IWorkItemResult wir) + { + IInternalWorkItemResult iwir = wir as IInternalWorkItemResult; + iwir.OnWorkItemStarted += new WorkItemStateCallback(OnWorkItemStartedCallback); + iwir.OnWorkItemCompleted += new WorkItemStateCallback(OnWorkItemCompletedCallback); + } + + public void OnSTPIsStarting() + { + lock (this) + { + if (_workItemsGroupStartInfo.StartSuspended) + { + return; + } + } + + for(int i = 0; i < _concurrency; ++i) + { + EnqueueToSTPNextWorkItem(null, false); + } + } + + private object FireOnIdle(object state) + { + FireOnIdleImpl(_onIdle); + return null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void FireOnIdleImpl(WorkItemsGroupIdleHandler onIdle) + { + if(null == onIdle) + { + return; + } + + Delegate[] delegates = onIdle.GetInvocationList(); + foreach(WorkItemsGroupIdleHandler eh in delegates) + { + try + { + eh(this); + } + // Ignore exceptions + catch{} + } + } + + private void OnWorkItemStartedCallback(WorkItem workItem) + { + lock(_lock) + { + ++_workItemsExecutingInStp; + } + } + + private void OnWorkItemCompletedCallback(WorkItem workItem) + { + EnqueueToSTPNextWorkItem(null, true); + } + + private void EnqueueToSTPNextWorkItem(WorkItem workItem) + { + EnqueueToSTPNextWorkItem(workItem, false); + } + + private void EnqueueToSTPNextWorkItem(WorkItem workItem, bool decrementWorkItemsInStpQueue) + { + lock(_lock) + { + // Got here from OnWorkItemCompletedCallback() + if (decrementWorkItemsInStpQueue) + { + --_workItemsInStpQueue; + + if(_workItemsInStpQueue < 0) + { + _workItemsInStpQueue = 0; + } + + --_workItemsExecutingInStp; + + if(_workItemsExecutingInStp < 0) + { + _workItemsExecutingInStp = 0; + } + } + + // If the work item is not null then enqueue it + if (null != workItem) + { + workItem.CanceledWorkItemsGroup = _canceledWorkItemsGroup; + + RegisterToWorkItemCompletion(workItem.GetWorkItemResult()); + _workItemsQueue.Enqueue(workItem); + //_stp.IncrementWorkItemsCount(); + + if ((1 == _workItemsQueue.Count) && + (0 == _workItemsInStpQueue)) + { + _stp.RegisterWorkItemsGroup(this); + Trace.WriteLine("WorkItemsGroup " + Name + " is NOT idle"); + _isIdleWaitHandle.Reset(); + } + } + + // If the work items queue of the group is empty than quit + if (0 == _workItemsQueue.Count) + { + if (0 == _workItemsInStpQueue) + { + _stp.UnregisterWorkItemsGroup(this); + Trace.WriteLine("WorkItemsGroup " + Name + " is idle"); + _isIdleWaitHandle.Set(); + _stp.QueueWorkItem(new WorkItemCallback(this.FireOnIdle)); + } + return; + } + + if (!_workItemsGroupStartInfo.StartSuspended) + { + if (_workItemsInStpQueue < _concurrency) + { + WorkItem nextWorkItem = _workItemsQueue.Dequeue() as WorkItem; + _stp.Enqueue(nextWorkItem, true); + ++_workItemsInStpQueue; + } + } + } + } + + #endregion + } + + #endregion +} diff --git a/SmartThreadPool/WorkItemsQueue.cs b/SmartThreadPool/WorkItemsQueue.cs new file mode 100644 index 0000000..2dad878 --- /dev/null +++ b/SmartThreadPool/WorkItemsQueue.cs @@ -0,0 +1,600 @@ +// Ami Bar +// amibar@gmail.com + +using System; +using System.Threading; + +namespace Amib.Threading.Internal +{ + #region WorkItemsQueue class + + /// + /// WorkItemsQueue class. + /// + public class WorkItemsQueue : IDisposable + { + #region Member variables + + /// + /// Waiters queue (implemented as stack). + /// + private WaiterEntry _headWaiterEntry = new WaiterEntry(); + + /// + /// Waiters count + /// + private int _waitersCount = 0; + + /// + /// Work items queue + /// + private PriorityQueue _workItems = new PriorityQueue(); + + /// + /// Indicate that work items are allowed to be queued + /// + private bool _isWorkItemsQueueActive = true; + + /// + /// Each thread in the thread pool keeps its own waiter entry. + /// + [ThreadStatic] + private static WaiterEntry _waiterEntry; + + /// + /// A flag that indicates if the WorkItemsQueue has been disposed. + /// + private bool _isDisposed = false; + + #endregion + + #region Public properties + + /// + /// Returns the current number of work items in the queue + /// + public int Count + { + get + { + lock(this) + { + ValidateNotDisposed(); + return _workItems.Count; + } + } + } + + /// + /// Returns the current number of waiters + /// + public int WaitersCount + { + get + { + lock(this) + { + ValidateNotDisposed(); + return _waitersCount; + } + } + } + + + #endregion + + #region Public methods + + /// + /// Enqueue a work item to the queue. + /// + public bool EnqueueWorkItem(WorkItem workItem) + { + // A work item cannot be null, since null is used in the + // WaitForWorkItem() method to indicate timeout or cancel + if (null == workItem) + { + throw new ArgumentNullException("workItem" , "workItem cannot be null"); + } + + bool enqueue = true; + + // First check if there is a waiter waiting for work item. During + // the check, timed out waiters are ignored. If there is no + // waiter then the work item is queued. + lock(this) + { + ValidateNotDisposed(); + + if (!_isWorkItemsQueueActive) + { + return false; + } + + while(_waitersCount > 0) + { + // Dequeue a waiter. + WaiterEntry waiterEntry = PopWaiter(); + + // Signal the waiter. On success break the loop + if (waiterEntry.Signal(workItem)) + { + enqueue = false; + break; + } + } + + if (enqueue) + { + // Enqueue the work item + _workItems.Enqueue(workItem); + } + } + return true; + } + + + /// + /// Waits for a work item or exits on timeout or cancel + /// + /// Timeout in milliseconds + /// Cancel wait handle + /// Returns true if the resource was granted + public WorkItem DequeueWorkItem( + int millisecondsTimeout, + WaitHandle cancelEvent) + { + /// This method cause the caller to wait for a work item. + /// If there is at least one waiting work item then the + /// method returns immidiately with true. + /// + /// If there are no waiting work items then the caller + /// is queued between other waiters for a work item to arrive. + /// + /// If a work item didn't come within millisecondsTimeout or + /// the user canceled the wait by signaling the cancelEvent + /// then the method returns false to indicate that the caller + /// didn't get a work item. + + WaiterEntry waiterEntry = null; + WorkItem workItem = null; + + lock(this) + { + ValidateNotDisposed(); + + // If there are waiting work items then take one and return. + if (_workItems.Count > 0) + { + workItem = _workItems.Dequeue() as WorkItem; + return workItem; + } + // No waiting work items ... + else + { + // Get the wait entry for the waiters queue + waiterEntry = GetThreadWaiterEntry(); + + // Put the waiter with the other waiters + PushWaiter(waiterEntry); + } + } + + // Prepare array of wait handle for the WaitHandle.WaitAny() + WaitHandle [] waitHandles = new WaitHandle [] { + waiterEntry.WaitHandle, + cancelEvent }; + + // Wait for an available resource, cancel event, or timeout. + + // During the wait we are supposes to exit the synchronization + // domain. (Placing true as the third argument of the WaitAny()) + // It just doesn't work, I don't know why, so I have lock(this) + // statments insted of one. + + int index = WaitHandle.WaitAny( + waitHandles, + millisecondsTimeout, + true); + + lock(this) + { + // success is true if it got a work item. + bool success = (0 == index); + + // The timeout variable is used only for readability. + // (We treat cancel as timeout) + bool timeout = !success; + + // On timeout update the waiterEntry that it is timed out + if (timeout) + { + // The Timeout() fails if the waiter has already been signaled + timeout = waiterEntry.Timeout(); + + // On timeout remove the waiter from the queue. + // Note that the complexity is O(1). + if(timeout) + { + RemoveWaiter(waiterEntry, false); + } + + // Again readability + success = !timeout; + } + + // On success return the work item + if (success) + { + workItem = waiterEntry.WorkItem; + + if (null == workItem) + { + workItem = _workItems.Dequeue() as WorkItem; + } + } + } + // On failure return null. + return workItem; + } + + /// + /// Cleanup the work items queue, hence no more work + /// items are allowed to be queue + /// + protected virtual void Cleanup() + { + lock(this) + { + // Deactivate only once + if (!_isWorkItemsQueueActive) + { + return; + } + + // Don't queue more work items + _isWorkItemsQueueActive = false; + + foreach(WorkItem workItem in _workItems) + { + workItem.DisposeOfState(); + } + + // Clear the work items that are already queued + _workItems.Clear(); + + // Note: + // I don't iterate over the queue and dispose of work items's states, + // since if a work item has a state object that is still in use in the + // application then I must not dispose it. + + // Tell the waiters that they were timed out. + // It won't signal them to exit, but to ignore their + // next work item. + while(_waitersCount > 0) + { + WaiterEntry waiterEntry = PopWaiter(); + waiterEntry.Timeout(); + } + } + } + + #endregion + + #region Private methods + + /// + /// Returns the WaiterEntry of the current thread + /// + /// + /// In order to avoid creation and destuction of WaiterEntry + /// objects each thread has its own WaiterEntry object. + private WaiterEntry GetThreadWaiterEntry() + { + if (null == _waiterEntry) + { + _waiterEntry = new WaiterEntry(); + } + _waiterEntry.Reset(); + return _waiterEntry; + } + + #region Waiters stack methods + + /// + /// Push a new waiter into the waiter's stack + /// + /// A waiter to put in the stack + public void PushWaiter(WaiterEntry newWaiterEntry) + { + // Remove the waiter if it is already in the stack and + // update waiter's count as needed + RemoveWaiter(newWaiterEntry, false); + + // If the stack is empty then newWaiterEntry is the new head of the stack + if (null == _headWaiterEntry._nextWaiterEntry) + { + _headWaiterEntry._nextWaiterEntry = newWaiterEntry; + newWaiterEntry._prevWaiterEntry = _headWaiterEntry; + + } + // If the stack is not empty then put newWaiterEntry as the new head + // of the stack. + else + { + // Save the old first waiter entry + WaiterEntry oldFirstWaiterEntry = _headWaiterEntry._nextWaiterEntry; + + // Update the links + _headWaiterEntry._nextWaiterEntry = newWaiterEntry; + newWaiterEntry._nextWaiterEntry = oldFirstWaiterEntry; + newWaiterEntry._prevWaiterEntry = _headWaiterEntry; + oldFirstWaiterEntry._prevWaiterEntry = newWaiterEntry; + } + + // Increment the number of waiters + ++_waitersCount; + } + + /// + /// Pop a waiter from the waiter's stack + /// + /// Returns the first waiter in the stack + private WaiterEntry PopWaiter() + { + // Store the current stack head + WaiterEntry oldFirstWaiterEntry = _headWaiterEntry._nextWaiterEntry; + + // Store the new stack head + WaiterEntry newHeadWaiterEntry = oldFirstWaiterEntry._nextWaiterEntry; + + // Update the old stack head list links and decrement the number + // waiters. + RemoveWaiter(oldFirstWaiterEntry, true); + + // Update the new stack head + _headWaiterEntry._nextWaiterEntry = newHeadWaiterEntry; + if (null != newHeadWaiterEntry) + { + newHeadWaiterEntry._prevWaiterEntry = _headWaiterEntry; + } + + // Return the old stack head + return oldFirstWaiterEntry; + } + + /// + /// Remove a waiter from the stack + /// + /// A waiter entry to remove + /// If true the waiter count is always decremented + private void RemoveWaiter(WaiterEntry waiterEntry, bool popDecrement) + { + // Store the prev entry in the list + WaiterEntry prevWaiterEntry = waiterEntry._prevWaiterEntry; + + // Store the next entry in the list + WaiterEntry nextWaiterEntry = waiterEntry._nextWaiterEntry; + + // A flag to indicate if we need to decrement the waiters count. + // If we got here from PopWaiter then we must decrement. + // If we got here from PushWaiter then we decrement only if + // the waiter was already in the stack. + bool decrementCounter = popDecrement; + + // Null the waiter's entry links + waiterEntry._prevWaiterEntry = null; + waiterEntry._nextWaiterEntry = null; + + // If the waiter entry had a prev link then update it. + // It also means that the waiter is already in the list and we + // need to decrement the waiters count. + if (null != prevWaiterEntry) + { + prevWaiterEntry._nextWaiterEntry = nextWaiterEntry; + decrementCounter = true; + } + + // If the waiter entry had a next link then update it. + // It also means that the waiter is already in the list and we + // need to decrement the waiters count. + if (null != nextWaiterEntry) + { + nextWaiterEntry._prevWaiterEntry = prevWaiterEntry; + decrementCounter = true; + } + + // Decrement the waiters count if needed + if (decrementCounter) + { + --_waitersCount; + } + } + + #endregion + + #endregion + + #region WaiterEntry class + + // A waiter entry in the _waiters queue. + public class WaiterEntry : IDisposable + { + #region Member variables + + /// + /// Event to signal the waiter that it got the work item. + /// + private AutoResetEvent _waitHandle = new AutoResetEvent(false); + + /// + /// Flag to know if this waiter already quited from the queue + /// because of a timeout. + /// + private bool _isTimedout = false; + + /// + /// Flag to know if the waiter was signaled and got a work item. + /// + private bool _isSignaled = false; + + /// + /// A work item that passed directly to the waiter withou going + /// through the queue + /// + private WorkItem _workItem = null; + + private bool _isDisposed = false; + + // Linked list members + internal WaiterEntry _nextWaiterEntry = null; + internal WaiterEntry _prevWaiterEntry = null; + + #endregion + + #region Construction + + public WaiterEntry() + { + Reset(); + } + + #endregion + + #region Public methods + + public WaitHandle WaitHandle + { + get { return _waitHandle; } + } + + public WorkItem WorkItem + { + get + { + lock(this) + { + return _workItem; + } + } + } + + /// + /// Signal the waiter that it got a work item. + /// + /// Return true on success + /// The method fails if Timeout() preceded its call + public bool Signal(WorkItem workItem) + { + lock(this) + { + if (!_isTimedout) + { + _workItem = workItem; + _isSignaled = true; + _waitHandle.Set(); + return true; + } + } + return false; + } + + /// + /// Mark the wait entry that it has been timed out + /// + /// Return true on success + /// The method fails if Signal() preceded its call + public bool Timeout() + { + lock(this) + { + // Time out can happen only if the waiter wasn't marked as + // signaled + if (!_isSignaled) + { + // We don't remove the waiter from the queue, the DequeueWorkItem + // method skips _waiters that were timed out. + _isTimedout = true; + return true; + } + } + return false; + } + + /// + /// Reset the wait entry so it can be used again + /// + public void Reset() + { + _workItem = null; + _isTimedout = false; + _isSignaled = false; + _waitHandle.Reset(); + } + + /// + /// Free resources + /// + public void Close() + { + if (null != _waitHandle) + { + _waitHandle.Close(); + _waitHandle = null; + } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + if (!_isDisposed) + { + Close(); + _isDisposed = true; + } + } + + ~WaiterEntry() + { + Dispose(); + } + + #endregion + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + if (!_isDisposed) + { + Cleanup(); + _isDisposed = true; + GC.SuppressFinalize(this); + } + } + + ~WorkItemsQueue() + { + Cleanup(); + } + + private void ValidateNotDisposed() + { + if(_isDisposed) + { + throw new ObjectDisposedException(GetType().ToString(), "The SmartThreadPool has been shutdown"); + } + } + + #endregion + } + + #endregion +} + diff --git a/TestSmartThreadPool/App.ico b/TestSmartThreadPool/App.ico new file mode 100644 index 0000000000000000000000000000000000000000..ef0b1248487e7ae5bcc5f99b51399b3b134157b3 GIT binary patch literal 1078 zcmcgrOHPD96f8zbhF%zMFeD~sPGLL(o4$r?6Bfe}SjsF6Tly57z)QGsV>p4+R?#$J zOiXk!7G3YXdi^1x3n0M;MUg{z-vD>ac^>pW0dB5S>^^ zRgrwuVi<;j9X}0tuZrZOmcpfB&R@`V-C4tAj@on@aL%D^oN@gT{jt;578yD;#vn6& zO9?d0SCgp($z`7tETyfVh9w(?Qz*gNC)K z6_@k=si2=0oOYG}94qg~C$c*DU3ED5K)>BC>*VvCEXe5dnstQc#DH4nma#)(Wcf>< z2aiQ>tvoe*DR`*i@uCoOP$8!J@VYNW-Go)POp`9>ewmiMgFjq>e#Ms&cqCunfcp>D K. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/TestSmartThreadPool/Form1.cs b/TestSmartThreadPool/Form1.cs new file mode 100644 index 0000000..e26b201 --- /dev/null +++ b/TestSmartThreadPool/Form1.cs @@ -0,0 +1,786 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Windows.Forms; + +using Amib.Threading; + +namespace TestSmartThreadPool +{ + /// + /// Summary description for Form1. + /// + public class Form1 : System.Windows.Forms.Form + { + private System.Windows.Forms.Button btnStart; + private System.Windows.Forms.Button btnStop; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label lblThreadInUse; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.NumericUpDown spinIdleTimeout; + private System.Windows.Forms.NumericUpDown spinMaxThreads; + private System.Windows.Forms.NumericUpDown spinMinThreads; + private System.Windows.Forms.NumericUpDown spinInterval; + private System.Windows.Forms.Timer timerPoll; + private System.ComponentModel.IContainer components; + private System.Windows.Forms.Label lblThreadsInPool; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.NumericUpDown spinConsumingTime; + + private SmartThreadPool _smartThreadPool; + private IWorkItemsGroup _workItemsGroup; + private System.Windows.Forms.Label lblWaitingCallbacks; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Label lblWorkItemsGenerated; + private bool running; + private long workItemsGenerated; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.Label lblWorkItemsCompleted; + private UsageControl.UsageControl usageThreadsInPool; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.GroupBox groupBox4; + private UsageControl.UsageHistoryControl usageHistorySTP; + private System.Diagnostics.PerformanceCounter pcActiveThreads; + private System.Diagnostics.PerformanceCounter pcInUseThreads; + private System.Diagnostics.PerformanceCounter pcQueuedWorkItems; + private System.Diagnostics.PerformanceCounter pcCompletedWorkItems; + private long workItemsCompleted; + + public Form1() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose( bool disposing ) + { + if( disposing ) + { + if (components != null) + { + components.Dispose(); + } + } + base.Dispose( disposing ); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1)); + this.btnStart = new System.Windows.Forms.Button(); + this.btnStop = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.lblThreadsInPool = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.spinIdleTimeout = new System.Windows.Forms.NumericUpDown(); + this.spinMaxThreads = new System.Windows.Forms.NumericUpDown(); + this.spinMinThreads = new System.Windows.Forms.NumericUpDown(); + this.spinInterval = new System.Windows.Forms.NumericUpDown(); + this.lblThreadInUse = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.timerPoll = new System.Windows.Forms.Timer(this.components); + this.spinConsumingTime = new System.Windows.Forms.NumericUpDown(); + this.label6 = new System.Windows.Forms.Label(); + this.lblWaitingCallbacks = new System.Windows.Forms.Label(); + this.label9 = new System.Windows.Forms.Label(); + this.label8 = new System.Windows.Forms.Label(); + this.label10 = new System.Windows.Forms.Label(); + this.lblWorkItemsGenerated = new System.Windows.Forms.Label(); + this.lblWorkItemsCompleted = new System.Windows.Forms.Label(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.usageThreadsInPool = new UsageControl.UsageControl(); + this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.usageHistorySTP = new UsageControl.UsageHistoryControl(); + this.pcActiveThreads = new System.Diagnostics.PerformanceCounter(); + this.pcInUseThreads = new System.Diagnostics.PerformanceCounter(); + this.pcQueuedWorkItems = new System.Diagnostics.PerformanceCounter(); + this.pcCompletedWorkItems = new System.Diagnostics.PerformanceCounter(); + ((System.ComponentModel.ISupportInitialize)(this.spinIdleTimeout)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.spinMaxThreads)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.spinMinThreads)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.spinInterval)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.spinConsumingTime)).BeginInit(); + this.groupBox2.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.groupBox4.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pcActiveThreads)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pcInUseThreads)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pcQueuedWorkItems)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pcCompletedWorkItems)).BeginInit(); + this.SuspendLayout(); + // + // btnStart + // + this.btnStart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnStart.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.btnStart.Location = new System.Drawing.Point(432, 352); + this.btnStart.Name = "btnStart"; + this.btnStart.Size = new System.Drawing.Size(72, 24); + this.btnStart.TabIndex = 0; + this.btnStart.Text = "Start"; + this.btnStart.Click += new System.EventHandler(this.btnStart_Click); + // + // btnStop + // + this.btnStop.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnStop.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.btnStop.Location = new System.Drawing.Point(520, 352); + this.btnStop.Name = "btnStop"; + this.btnStop.Size = new System.Drawing.Size(72, 24); + this.btnStop.TabIndex = 1; + this.btnStop.Text = "Stop"; + this.btnStop.Click += new System.EventHandler(this.btnStop_Click); + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label1.Location = new System.Drawing.Point(104, 256); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(104, 24); + this.label1.TabIndex = 2; + this.label1.Text = "Minimum Threads"; + this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label3 + // + this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label3.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label3.Location = new System.Drawing.Point(104, 288); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(104, 24); + this.label3.TabIndex = 4; + this.label3.Text = "Maximum Threads"; + this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label4 + // + this.label4.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label4.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label4.Location = new System.Drawing.Point(104, 320); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(120, 24); + this.label4.TabIndex = 5; + this.label4.Text = "Idle timeout (Seconds)"; + this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label2 + // + this.label2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label2.Location = new System.Drawing.Point(8, 16); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(72, 24); + this.label2.TabIndex = 3; + this.label2.Text = "In pool (Red)"; + this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // lblThreadsInPool + // + this.lblThreadsInPool.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.lblThreadsInPool.Location = new System.Drawing.Point(80, 16); + this.lblThreadsInPool.Name = "lblThreadsInPool"; + this.lblThreadsInPool.Size = new System.Drawing.Size(80, 24); + this.lblThreadsInPool.TabIndex = 11; + this.lblThreadsInPool.Text = "XXXXXXXXX"; + this.lblThreadsInPool.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label5 + // + this.label5.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label5.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label5.Location = new System.Drawing.Point(336, 258); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(280, 24); + this.label5.TabIndex = 12; + this.label5.Text = "Interval between work item production (milliseconds)"; + this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // spinIdleTimeout + // + this.spinIdleTimeout.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.spinIdleTimeout.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.spinIdleTimeout.Location = new System.Drawing.Point(8, 320); + this.spinIdleTimeout.Minimum = new System.Decimal(new int[] { + 1, + 0, + 0, + 0}); + this.spinIdleTimeout.Name = "spinIdleTimeout"; + this.spinIdleTimeout.Size = new System.Drawing.Size(88, 29); + this.spinIdleTimeout.TabIndex = 15; + this.spinIdleTimeout.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.spinIdleTimeout.Value = new System.Decimal(new int[] { + 10, + 0, + 0, + 0}); + // + // spinMaxThreads + // + this.spinMaxThreads.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.spinMaxThreads.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.spinMaxThreads.Location = new System.Drawing.Point(8, 288); + this.spinMaxThreads.Minimum = new System.Decimal(new int[] { + 1, + 0, + 0, + 0}); + this.spinMaxThreads.Name = "spinMaxThreads"; + this.spinMaxThreads.Size = new System.Drawing.Size(88, 29); + this.spinMaxThreads.TabIndex = 14; + this.spinMaxThreads.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.spinMaxThreads.Value = new System.Decimal(new int[] { + 10, + 0, + 0, + 0}); + this.spinMaxThreads.ValueChanged += new System.EventHandler(this.spinMaxThreads_ValueChanged); + // + // spinMinThreads + // + this.spinMinThreads.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.spinMinThreads.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.spinMinThreads.Location = new System.Drawing.Point(8, 256); + this.spinMinThreads.Name = "spinMinThreads"; + this.spinMinThreads.Size = new System.Drawing.Size(88, 29); + this.spinMinThreads.TabIndex = 13; + this.spinMinThreads.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.spinMinThreads.ValueChanged += new System.EventHandler(this.spinMinThreads_ValueChanged); + // + // spinInterval + // + this.spinInterval.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.spinInterval.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.spinInterval.Increment = new System.Decimal(new int[] { + 100, + 0, + 0, + 0}); + this.spinInterval.Location = new System.Drawing.Point(240, 256); + this.spinInterval.Maximum = new System.Decimal(new int[] { + 100000, + 0, + 0, + 0}); + this.spinInterval.Name = "spinInterval"; + this.spinInterval.Size = new System.Drawing.Size(88, 29); + this.spinInterval.TabIndex = 16; + this.spinInterval.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.spinInterval.Value = new System.Decimal(new int[] { + 100, + 0, + 0, + 0}); + // + // lblThreadInUse + // + this.lblThreadInUse.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.lblThreadInUse.Location = new System.Drawing.Point(80, 40); + this.lblThreadInUse.Name = "lblThreadInUse"; + this.lblThreadInUse.Size = new System.Drawing.Size(80, 24); + this.lblThreadInUse.TabIndex = 18; + this.lblThreadInUse.Text = "XXXXXXXXX"; + this.lblThreadInUse.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label7 + // + this.label7.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label7.Location = new System.Drawing.Point(8, 40); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(80, 24); + this.label7.TabIndex = 17; + this.label7.Text = "Used (Green)"; + this.label7.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // timerPoll + // + this.timerPoll.Interval = 500; + this.timerPoll.Tick += new System.EventHandler(this.timer1_Tick); + // + // spinConsumingTime + // + this.spinConsumingTime.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.spinConsumingTime.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.spinConsumingTime.Increment = new System.Decimal(new int[] { + 100, + 0, + 0, + 0}); + this.spinConsumingTime.Location = new System.Drawing.Point(240, 288); + this.spinConsumingTime.Maximum = new System.Decimal(new int[] { + 100000, + 0, + 0, + 0}); + this.spinConsumingTime.Name = "spinConsumingTime"; + this.spinConsumingTime.Size = new System.Drawing.Size(88, 29); + this.spinConsumingTime.TabIndex = 20; + this.spinConsumingTime.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.spinConsumingTime.Value = new System.Decimal(new int[] { + 100, + 0, + 0, + 0}); + // + // label6 + // + this.label6.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label6.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label6.Location = new System.Drawing.Point(336, 290); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(216, 24); + this.label6.TabIndex = 19; + this.label6.Text = "Work item consuming time (milliseconds)"; + this.label6.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // lblWaitingCallbacks + // + this.lblWaitingCallbacks.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.lblWaitingCallbacks.Location = new System.Drawing.Point(64, 16); + this.lblWaitingCallbacks.Name = "lblWaitingCallbacks"; + this.lblWaitingCallbacks.Size = new System.Drawing.Size(80, 24); + this.lblWaitingCallbacks.TabIndex = 22; + this.lblWaitingCallbacks.Text = "XXXXXXXXX"; + this.lblWaitingCallbacks.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label9 + // + this.label9.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label9.Location = new System.Drawing.Point(8, 16); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(48, 24); + this.label9.TabIndex = 21; + this.label9.Text = "Queued"; + this.label9.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label8 + // + this.label8.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label8.Location = new System.Drawing.Point(8, 40); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(64, 24); + this.label8.TabIndex = 25; + this.label8.Text = "Generated"; + this.label8.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label10 + // + this.label10.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.label10.Location = new System.Drawing.Point(8, 64); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(64, 24); + this.label10.TabIndex = 26; + this.label10.Text = "Completed"; + this.label10.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // lblWorkItemsGenerated + // + this.lblWorkItemsGenerated.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.lblWorkItemsGenerated.Location = new System.Drawing.Point(64, 40); + this.lblWorkItemsGenerated.Name = "lblWorkItemsGenerated"; + this.lblWorkItemsGenerated.Size = new System.Drawing.Size(80, 24); + this.lblWorkItemsGenerated.TabIndex = 27; + this.lblWorkItemsGenerated.Text = "XXXXXXXXX"; + this.lblWorkItemsGenerated.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lblWorkItemsCompleted + // + this.lblWorkItemsCompleted.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.lblWorkItemsCompleted.Location = new System.Drawing.Point(64, 64); + this.lblWorkItemsCompleted.Name = "lblWorkItemsCompleted"; + this.lblWorkItemsCompleted.Size = new System.Drawing.Size(80, 24); + this.lblWorkItemsCompleted.TabIndex = 28; + this.lblWorkItemsCompleted.Text = "XXXXXXXXX"; + this.lblWorkItemsCompleted.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // groupBox2 + // + this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.groupBox2.Controls.Add(this.lblWaitingCallbacks); + this.groupBox2.Controls.Add(this.label9); + this.groupBox2.Controls.Add(this.label8); + this.groupBox2.Controls.Add(this.label10); + this.groupBox2.Controls.Add(this.lblWorkItemsGenerated); + this.groupBox2.Controls.Add(this.lblWorkItemsCompleted); + this.groupBox2.Location = new System.Drawing.Point(8, 144); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(152, 96); + this.groupBox2.TabIndex = 33; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Work items"; + // + // groupBox3 + // + this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.groupBox3.Controls.Add(this.lblThreadInUse); + this.groupBox3.Controls.Add(this.label7); + this.groupBox3.Controls.Add(this.lblThreadsInPool); + this.groupBox3.Controls.Add(this.label2); + this.groupBox3.Location = new System.Drawing.Point(176, 144); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(168, 72); + this.groupBox3.TabIndex = 34; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "Threads"; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left))); + this.groupBox1.Controls.Add(this.usageThreadsInPool); + this.groupBox1.Location = new System.Drawing.Point(8, 8); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(80, 128); + this.groupBox1.TabIndex = 35; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "STP Usage"; + // + // usageThreadsInPool + // + this.usageThreadsInPool.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.usageThreadsInPool.BackColor = System.Drawing.Color.Black; + this.usageThreadsInPool.Location = new System.Drawing.Point(20, 16); + this.usageThreadsInPool.Maximum = 25; + this.usageThreadsInPool.Name = "usageThreadsInPool"; + this.usageThreadsInPool.Size = new System.Drawing.Size(41, 104); + this.usageThreadsInPool.TabIndex = 37; + this.usageThreadsInPool.Value1 = 1; + this.usageThreadsInPool.Value2 = 24; + // + // groupBox4 + // + this.groupBox4.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox4.Controls.Add(this.usageHistorySTP); + this.groupBox4.Location = new System.Drawing.Point(104, 8); + this.groupBox4.Name = "groupBox4"; + this.groupBox4.Size = new System.Drawing.Size(494, 128); + this.groupBox4.TabIndex = 36; + this.groupBox4.TabStop = false; + this.groupBox4.Text = "STP Usage History"; + // + // usageHistorySTP + // + this.usageHistorySTP.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.usageHistorySTP.BackColor = System.Drawing.Color.Black; + this.usageHistorySTP.Location = new System.Drawing.Point(8, 16); + this.usageHistorySTP.Maximum = 100; + this.usageHistorySTP.Name = "usageHistorySTP"; + this.usageHistorySTP.Size = new System.Drawing.Size(480, 104); + this.usageHistorySTP.TabIndex = 0; + // + // pcActiveThreads + // + this.pcActiveThreads.CategoryName = "SmartThreadPool"; + this.pcActiveThreads.CounterName = "Active threads"; + this.pcActiveThreads.InstanceName = "Test SmartThreadPool"; + // + // pcInUseThreads + // + this.pcInUseThreads.CategoryName = "SmartThreadPool"; + this.pcInUseThreads.CounterName = "In use threads"; + this.pcInUseThreads.InstanceName = "Test SmartThreadPool"; + // + // pcQueuedWorkItems + // + this.pcQueuedWorkItems.CategoryName = "SmartThreadPool"; + this.pcQueuedWorkItems.CounterName = "Work Items in queue"; + this.pcQueuedWorkItems.InstanceName = "Test SmartThreadPool"; + // + // pcCompletedWorkItems + // + this.pcCompletedWorkItems.CategoryName = "SmartThreadPool"; + this.pcCompletedWorkItems.CounterName = "Work Items processed"; + this.pcCompletedWorkItems.InstanceName = "Test SmartThreadPool"; + // + // Form1 + // + this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); + this.ClientSize = new System.Drawing.Size(608, 382); + this.Controls.Add(this.groupBox4); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox3); + this.Controls.Add(this.spinConsumingTime); + this.Controls.Add(this.label6); + this.Controls.Add(this.spinInterval); + this.Controls.Add(this.spinIdleTimeout); + this.Controls.Add(this.spinMaxThreads); + this.Controls.Add(this.spinMinThreads); + this.Controls.Add(this.label5); + this.Controls.Add(this.label4); + this.Controls.Add(this.label3); + this.Controls.Add(this.label1); + this.Controls.Add(this.btnStop); + this.Controls.Add(this.btnStart); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MinimumSize = new System.Drawing.Size(616, 416); + this.Name = "Form1"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Test Smart Thread Pool"; + this.Closing += new System.ComponentModel.CancelEventHandler(this.Form1_Closing); + this.Load += new System.EventHandler(this.Form1_Load); + ((System.ComponentModel.ISupportInitialize)(this.spinIdleTimeout)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.spinMaxThreads)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.spinMinThreads)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.spinInterval)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.spinConsumingTime)).EndInit(); + this.groupBox2.ResumeLayout(false); + this.groupBox3.ResumeLayout(false); + this.groupBox1.ResumeLayout(false); + this.groupBox4.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pcActiveThreads)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pcInUseThreads)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pcQueuedWorkItems)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pcCompletedWorkItems)).EndInit(); + this.ResumeLayout(false); + + } + #endregion + + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + bool runApplication = InitializePerformanceCounters(); + if (!runApplication) + { + return; + } + + Application.Run(new Form1()); + } + + // This method is a work around for the Peformance Counter issue. + // When the first SmartThreadPool is created with a Peformance + // Counter name on a machine, it creates the SmartThreadPool + // Peformance Counter category. In this demo I use thes Performance + // Counters to update the GUI. + // The issue is that if this demo runs for the first time on the + // machine, it creates the Peformance Counter category and then + // uses it. + // I don't know why, but every time the demo runs for the first + // time on a machine, it fails to connect to the Peformance Counters, + // because it can't find the Peformance Counter category. + // The work around is to check if the category exists, and if not + // create a SmartThreadPool instance that will create the category. + // After that I spawn another process that runs the demo. + // I tried the another work around and thats to check for the category + // existance, run a second process that will create the category, + // and then continue with the first process, but it doesn't work. + // Thank you for reading the whole comment. If you have another way + // to solve this issue please contact me: amibar@gmail.com. + private static bool InitializePerformanceCounters() + { + if (!PerformanceCounterCategory.Exists("SmartThreadPool")) + { + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.PerformanceCounterInstanceName = "Test SmartThreadPool"; + + SmartThreadPool stp = new SmartThreadPool(stpStartInfo); + stp.Shutdown(); + + if (!PerformanceCounterCategory.Exists("SmartThreadPool")) + { + MessageBox.Show("Failed to create Performance Counters.", "Test Smart Thread Pool", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + + Process process = new Process(); + process.StartInfo.FileName = Application.ExecutablePath; + + try + { + process.Start(); + } + catch(Exception e) + { + e.GetHashCode(); + MessageBox.Show("If this is the first time you get this message,\r\nplease try to run the demo again.", "Test Smart Thread Pool"); + } + + return false; + } + + return true; + } + + private Thread workItemsProducerThread; + + private void btnStart_Click(object sender, System.EventArgs e) + { + UpdateControls(true); + workItemsCompleted = 0; + workItemsGenerated = 0; + + STPStartInfo stpStartInfo = new STPStartInfo(); + stpStartInfo.IdleTimeout = Convert.ToInt32(spinIdleTimeout.Value)*1000; + stpStartInfo.MaxWorkerThreads = Convert.ToInt32(spinMaxThreads.Value); + stpStartInfo.MinWorkerThreads = Convert.ToInt32(spinMinThreads.Value); + stpStartInfo.PerformanceCounterInstanceName = "Test SmartThreadPool"; + + _smartThreadPool = new SmartThreadPool(stpStartInfo); + + //_workItemsGroup = _smartThreadPool.CreateWorkItemsGroup(1); + _workItemsGroup = _smartThreadPool; + + workItemsProducerThread = new Thread(new ThreadStart(this.WorkItemsProducer)); + workItemsProducerThread.IsBackground = true; + workItemsProducerThread.Start(); + } + + private void btnStop_Click(object sender, System.EventArgs e) + { + running = false; + workItemsProducerThread.Join(); + + _smartThreadPool.Shutdown(); + _smartThreadPool.Dispose(); + _smartThreadPool = null; + GC.Collect(); + GC.WaitForPendingFinalizers(); + UpdateControls(false); + } + + private void Form1_Load(object sender, System.EventArgs e) + { + UpdateControls(false); + } + + private void UpdateControls(bool start) + { + running = start; + spinMinThreads.Enabled = !start; + spinMaxThreads.Enabled = !start; + spinIdleTimeout.Enabled = !start; + btnStart.Enabled = !start; + + btnStop.Enabled = start; + timerPoll.Enabled = start; + + lblThreadInUse.Text = "0"; + lblThreadsInPool.Text = "0"; + lblWaitingCallbacks.Text = "0"; + usageThreadsInPool.Maximum = Convert.ToInt32(spinMaxThreads.Value); + usageThreadsInPool.Value1 = 0; + usageThreadsInPool.Value2 = 0; + lblWorkItemsCompleted.Text = "0"; + lblWorkItemsGenerated.Text = "0"; + usageHistorySTP.Reset(); + usageHistorySTP.Maximum = usageThreadsInPool.Maximum; + } + + private void spinMinThreads_ValueChanged(object sender, System.EventArgs e) + { + if (spinMinThreads.Value > spinMaxThreads.Value) + { + spinMaxThreads.Value = spinMinThreads.Value; + } + } + + private void spinMaxThreads_ValueChanged(object sender, System.EventArgs e) + { + if (spinMaxThreads.Value < spinMinThreads.Value) + { + spinMinThreads.Value = spinMaxThreads.Value; + } + usageThreadsInPool.Maximum = Convert.ToInt32(spinMaxThreads.Value); + } + + private void timer1_Tick(object sender, System.EventArgs e) + { + SmartThreadPool stp = _smartThreadPool; + if (null == stp) + { + return; + } + + int threadsInUse = (int)pcInUseThreads.NextValue(); + int threadsInPool = (int)pcActiveThreads.NextValue(); + + lblThreadInUse.Text = threadsInUse.ToString(); + lblThreadsInPool.Text = threadsInPool.ToString(); + lblWaitingCallbacks.Text = pcQueuedWorkItems.NextValue().ToString(); //stp.WaitingCallbacks.ToString(); + usageThreadsInPool.Value1 = threadsInUse; + usageThreadsInPool.Value2 = threadsInPool; + lblWorkItemsCompleted.Text = pcCompletedWorkItems.NextValue().ToString(); + lblWorkItemsGenerated.Text = workItemsGenerated.ToString(); + usageHistorySTP.AddValues(threadsInUse, threadsInPool); + } + + private void WorkItemsProducer() + { + WorkItemCallback workItemCallback = new WorkItemCallback(this.DoWork); + while(running) + { + IWorkItemsGroup workItemsGroup = _workItemsGroup; + if (null == workItemsGroup) + { + return; + } + + try + { + workItemCallback = new WorkItemCallback(this.DoWork); + workItemsGroup.QueueWorkItem(workItemCallback); + } + catch(ObjectDisposedException e) + { + e.GetHashCode(); + break; + } + workItemsGenerated++; + Thread.Sleep(Convert.ToInt32(spinInterval.Value)); + } + } + + private object DoWork(object obj) + { + Thread.Sleep(Convert.ToInt32(spinConsumingTime.Value)); + Interlocked.Increment(ref workItemsCompleted); + return null; + } + + private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + if (null != _smartThreadPool) + { + _smartThreadPool.Shutdown(); + _smartThreadPool = null; + _workItemsGroup = null; + } + } + } +} diff --git a/TestSmartThreadPool/Form1.resx b/TestSmartThreadPool/Form1.resx new file mode 100644 index 0000000..30f3ad2 --- /dev/null +++ b/TestSmartThreadPool/Form1.resx @@ -0,0 +1,485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + Private + + + 17, 17 + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + Private + + + 8, 8 + + + True + + + False + + + True + + + Private + + + Private + + + 8, 8 + + + True + + + False + + + True + + + Private + + + Private + + + 8, 8 + + + True + + + False + + + True + + + Private + + + False + + + Private + + + Private + + + Private + + + 8, 8 + + + True + + + False + + + True + + + Private + + + False + + + Private + + + Private + + + Private + + + Private + + + 112, 17 + + + Private + + + Private + + + 245, 17 + + + Private + + + Private + + + 377, 17 + + + Private + + + Private + + + 533, 17 + + + False + + + (Default) + + + False + + + False + + + 8, 8 + + + True + + + 59 + + + True + + + Form1 + + + Private + + + + AAABAAIAICAQAAAAAADoAgAAJgAAABAQEAAAAAAAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC + AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/ + AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd3d3d3d3d3d3d3d3d3dwBEREREREREREREREREREcAT/ + +q///6r///qv////9HAE//qv//+q///6r/////RwBP/6r///qv//+q/////0cAT/+q///6r///qv//// + 9HAE//+q///6r///qv////RwBP//qv//+q///6r////0cAT//6r///qv//+q////9HAE//+q///6r/// + qv////RwBP/6r///qv//+q/////0cAT/+q///6r///qv////9HAE//qv//+q///6r/////RwBP/6r/// + qv//+q/////0cAT//6r///qv//+q////9HAE//+q///6r///qv////RwBP//qv//+q///6r////0cAT/ + /6r///qv//+q////9HAE//////////////////RwBP/////////////////0cASIiIiIiIiIiIiIiIiI + hHAERERERERERERERERERERwBExMTExMTExMTE7Ozkl0cATMzMzMzMzMzMzMzMzMxAAARERERERERERE + REREREAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + /////////////8AAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAA + AAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAA8AAAAf///////////// + //8oAAAAEAAAACAAAAABAAQAAAAAAIAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA + AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAACqAACqAACqqpmgCp + mqCgAJAKCgAJCqmZAAmpAACZkAAAAKkAAACQAAAAkAAAAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAVQ + AAUABQAAUAUABQAFAAAAVQAFAAVVAFUAAAUABQBQUAUABQAFAFAFUAVVVQVVAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAnu8AAG7vAADO4wAAPu0AAG7tAACYIwAA//8AAA== + + + \ No newline at end of file diff --git a/TestSmartThreadPool/TestSmartThreadPool.csproj b/TestSmartThreadPool/TestSmartThreadPool.csproj new file mode 100644 index 0000000..fc57450 --- /dev/null +++ b/TestSmartThreadPool/TestSmartThreadPool.csproj @@ -0,0 +1,121 @@ + + + Local + 8.0.50727 + 2.0 + {976DB12F-9198-4AD9-981A-1652615C9B0D} + Debug + AnyCPU + App.ico + + + TestSmartThreadPool + + + JScript + Grid + IE50 + false + WinExe + TestSmartThreadPool + OnBuildSuccess + TestSmartThreadPool.Form1 + + + + + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + System + + + System.Data + + + System.Drawing + + + System.Windows.Forms + + + System.XML + + + SmartThreadPool + {8684FC56-A679-4E2E-8F96-E172FB062EB6} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + UsageControl + {C11A4561-CCB5-4C96-8DF2-B804031D89D8} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + + Code + + + Form + + + Form1.cs + Designer + + + + + + + + + + \ No newline at end of file diff --git a/UsageControl/AssemblyInfo.cs b/UsageControl/AssemblyInfo.cs new file mode 100644 index 0000000..9f89a32 --- /dev/null +++ b/UsageControl/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/UsageControl/UsageControl.cs b/UsageControl/UsageControl.cs new file mode 100644 index 0000000..cc86272 --- /dev/null +++ b/UsageControl/UsageControl.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Data; +using System.Windows.Forms; + +namespace UsageControl +{ + /// + /// Summary description for UsageControl. + /// + public class UsageControl : System.Windows.Forms.UserControl + { + private System.ComponentModel.IContainer components = null; + + private const int ovalWidth = 18; + private const int columns = 2; + + private int fixedWidth; + private int rows = 1; + + private int min = 0; // Minimum value for progress range + private int max = 100; // Maximum value for progress range + private int val1 = 0; // Current progress + private int val2 = 0; // Current progress + + public UsageControl() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + EnableDoubleBuffering(); + fixedWidth = 2 + (ovalWidth+1)*columns + 1; + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose( bool disposing ) + { + if( disposing ) + { + if(components != null) + { + components.Dispose(); + } + } + base.Dispose( disposing ); + } + + private void EnableDoubleBuffering() + { + // Set the value of the double-buffering style bits to true. + this.SetStyle(ControlStyles.DoubleBuffer | + ControlStyles.UserPaint | + ControlStyles.AllPaintingInWmPaint, + true); + this.UpdateStyles(); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + // + // UsageControl + // + this.BackColor = System.Drawing.Color.Black; + this.Name = "UsageControl"; + this.Size = new System.Drawing.Size(192, 160); + + } + #endregion + + protected override void OnResize(EventArgs e) + { + Width = fixedWidth; + rows = (ClientRectangle.Height / 4) - 1; + // Invalidate the control to get a repaint. + this.Invalidate(); + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + + int percent1 = ((val1 - min) * 100) / (max - min); + int percent2 = ((val2 - min) * 100) / (max - min); + + int filledCount1 = (rows * percent1) / 100; + int filledCount2 = (rows * percent2) / 100; + int diff = Math.Abs(filledCount1 - filledCount2); + + int emptyCount = rows - Math.Max(filledCount1, filledCount2); + + int i = 0; + + for(i = 0; i < filledCount1; ++i) + { + for(int j = 0; j < columns; ++j) + { + DrawOval( + g, + Pens.LawnGreen, + 2 + j*(1+ovalWidth), + ClientRectangle.Bottom - 5 - i*4); + } + } + + for(; i < filledCount2; ++i) + { + for(int j = 0; j < columns; ++j) + { + DrawOval( + g, + Pens.Red, + 2 + j*(1+ovalWidth), + ClientRectangle.Bottom - 5 - i*4); + } + } + + for(; i < rows; ++i) + { + for(int j = 0; j < columns; ++j) + { + DrawOval( + g, + Pens.Green, + 2 + j*(1+ovalWidth), + ClientRectangle.Bottom - 5 - i*4); + } + } + + // Draw a three-dimensional border around the control. + Draw3DBorder(g); + + // Clean up. + //g.Dispose(); + base.OnPaint (e); + } + + private void DrawOval(Graphics g, Pen pen, int x, int y) + { + g.DrawLine(pen, x+1 , y, x+ovalWidth-2, y); + g.DrawLine(pen, x, y+1, x+ovalWidth-1, y+1); + g.DrawLine(pen, x+1, y+2, x+ovalWidth-2, y+2); + } + + public int Maximum + { + get + { + return max; + } + + set + { + // Make sure that the maximum value is never set lower than the minimum value. + if (value < min) + { + min = value; + } + + max = value; + + // Make sure that value is still in range. + if (val1 > max) + { + val1 = max; + } + + // Invalidate the control to get a repaint. + this.Invalidate(); + } + } + + public int Value1 + { + get + { + return val1; + } + + set + { + int oldValue = val1; + + // Make sure that the value does not stray outside the valid range. + if (value < min) + { + val1 = min; + } + else if (value > max) + { + val1 = max; + } + else + { + val1 = value; + } + Invalidate(); +/* + // Invalidate only the changed area. + float percent; + + Rectangle newValueRect = this.ClientRectangle; + Rectangle oldValueRect = this.ClientRectangle; + + // Use a new value to calculate the rectangle for progress. + percent = (float)(val1 - min) / (float)(max - min); + newValueRect.Width = (int)((float)newValueRect.Width * percent); + + // Use an old value to calculate the rectangle for progress. + percent = (float)(oldValue - min) / (float)(max - min); + oldValueRect.Width = (int)((float)oldValueRect.Width * percent); + + Rectangle updateRect = new Rectangle(); + + // Find only the part of the screen that must be updated. + if (newValueRect.Width > oldValueRect.Width) + { + updateRect.X = oldValueRect.Size.Width; + updateRect.Width = newValueRect.Width - oldValueRect.Width; + } + else + { + updateRect.X = newValueRect.Size.Width; + updateRect.Width = oldValueRect.Width - newValueRect.Width; + } + + updateRect.Height = this.Height; + + // Invalidate the intersection region only. + this.Invalidate(updateRect); +*/ + } + } + + public int Value2 + { + get + { + return val2; + } + + set + { + int oldValue = val2; + + // Make sure that the value does not stray outside the valid range. + if (value < min) + { + val2 = min; + } + else if (value > max) + { + val2 = max; + } + else + { + val2 = value; + } + Invalidate(); + + /* + // Invalidate only the changed area. + float percent; + + Rectangle newValueRect = this.ClientRectangle; + Rectangle oldValueRect = this.ClientRectangle; + + // Use a new value to calculate the rectangle for progress. + percent = (float)(val1 - min) / (float)(max - min); + newValueRect.Width = (int)((float)newValueRect.Width * percent); + + // Use an old value to calculate the rectangle for progress. + percent = (float)(oldValue - min) / (float)(max - min); + oldValueRect.Width = (int)((float)oldValueRect.Width * percent); + + Rectangle updateRect = new Rectangle(); + + // Find only the part of the screen that must be updated. + if (newValueRect.Width > oldValueRect.Width) + { + updateRect.X = oldValueRect.Size.Width; + updateRect.Width = newValueRect.Width - oldValueRect.Width; + } + else + { + updateRect.X = newValueRect.Size.Width; + updateRect.Width = oldValueRect.Width - newValueRect.Width; + } + + updateRect.Height = this.Height; + + // Invalidate the intersection region only. + this.Invalidate(updateRect); + */ + } + } + + private void Draw3DBorder(Graphics g) + { + int PenWidth = (int)Pens.White.Width; + + g.DrawLine(Pens.DarkGray, + new Point(this.ClientRectangle.Left, this.ClientRectangle.Top), + new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Top)); + g.DrawLine(Pens.DarkGray, + new Point(this.ClientRectangle.Left, this.ClientRectangle.Top), + new Point(this.ClientRectangle.Left, this.ClientRectangle.Height - PenWidth)); + g.DrawLine(Pens.White, + new Point(this.ClientRectangle.Left, this.ClientRectangle.Height - PenWidth), + new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Height - PenWidth)); + g.DrawLine(Pens.White, + new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Top), + new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Height - PenWidth)); + } + + + } +} diff --git a/UsageControl/UsageControl.csproj b/UsageControl/UsageControl.csproj new file mode 100644 index 0000000..404f499 --- /dev/null +++ b/UsageControl/UsageControl.csproj @@ -0,0 +1,119 @@ + + + Local + 8.0.50727 + 2.0 + {C11A4561-CCB5-4C96-8DF2-B804031D89D8} + Debug + AnyCPU + + + + + UsageControl + + + JScript + Grid + IE50 + false + Library + UsageControl + OnBuildSuccess + + + + + + + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + System + + + System.Data + + + System.Drawing + + + System.Windows.Forms + + + System.XML + + + + + Code + + + UserControl + + + UserControl + + + UsageControl.cs + Designer + + + UsageHistoryControl.cs + Designer + + + + + + + + + + \ No newline at end of file diff --git a/UsageControl/UsageControl.resx b/UsageControl/UsageControl.resx new file mode 100644 index 0000000..e35e03a --- /dev/null +++ b/UsageControl/UsageControl.resx @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + True + + + True + + + 80 + + + (Default) + + + False + + + UsageControl + + + Private + + + 8, 8 + + \ No newline at end of file diff --git a/UsageControl/UsageHistoryControl.cs b/UsageControl/UsageHistoryControl.cs new file mode 100644 index 0000000..b5d454f --- /dev/null +++ b/UsageControl/UsageHistoryControl.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Windows.Forms; + +namespace UsageControl +{ + /// + /// Summary description for UsageHistoryControl. + /// + public class UsageHistoryControl : System.Windows.Forms.UserControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + private const int squareWidth = 12; + private const int maxLastValuesCount = 2000; + private const int shiftStep = 3; + + private int shift = 0; + + private int [] lastValues1 = new int[maxLastValuesCount]; + private int [] lastValues2 = new int[maxLastValuesCount]; + private int nextValueIndex; + private int lastValuesCount; + + private int max = 100; + private int min = 0; + + public UsageHistoryControl() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + EnableDoubleBuffering(); + Reset(); + } + + public void Reset() + { + lastValues1.Initialize(); + lastValues2.Initialize(); + nextValueIndex = 0; + lastValuesCount = 0; + Invalidate(); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose( bool disposing ) + { + if( disposing ) + { + if(components != null) + { + components.Dispose(); + } + } + base.Dispose( disposing ); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + // + // UsageHistoryControl + // + this.BackColor = System.Drawing.Color.Black; + this.Name = "UsageHistoryControl"; + + } + #endregion + + private void EnableDoubleBuffering() + { + // Set the value of the double-buffering style bits to true. + this.SetStyle(ControlStyles.DoubleBuffer | + ControlStyles.UserPaint | + ControlStyles.AllPaintingInWmPaint, + true); + this.UpdateStyles(); + } + + protected override void OnResize(EventArgs e) + { + // Invalidate the control to get a repaint. + this.Invalidate(); + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + + for(int i = 0; i <= ClientRectangle.Width+squareWidth; i += squareWidth) + { + g.DrawLine(Pens.Green, i-shift, 0, i-shift, ClientRectangle.Height); + } + + for(int i = 0; i < ClientRectangle.Height; i += squareWidth) + { + g.DrawLine(Pens.Green, 0, i, ClientRectangle.Width, i); + } + + int startValueIndex = (nextValueIndex-1+maxLastValuesCount)%maxLastValuesCount; + + int prevVal1 = GetRelativeValue(lastValues1[startValueIndex]); + int prevVal2 = GetRelativeValue(lastValues2[startValueIndex]); + + for(int i = 1; i < lastValuesCount; ++i) + { + int index = nextValueIndex - 1 - i; + if (index < 0) + { + index += maxLastValuesCount; + } + + int val1 = GetRelativeValue(lastValues1[index]); + int val2 = GetRelativeValue(lastValues2[index]); + + g.DrawLine( + Pens.Red, + ClientRectangle.Width-(i-1)*shiftStep, ClientRectangle.Height-prevVal2, + ClientRectangle.Width-i*shiftStep, ClientRectangle.Height-val2); + + g.DrawLine( + Pens.LawnGreen, + ClientRectangle.Width-(i-1)*shiftStep, ClientRectangle.Height-prevVal1, + ClientRectangle.Width-i*shiftStep, ClientRectangle.Height-val1); + + prevVal1 = val1; + prevVal2 = val2; + } + } + + private int GetRelativeValue(int val) + { + int result = val * (ClientRectangle.Height-2) / max + 1; + return result; + } + + public void AddValues(int val1, int val2) + { + lastValues1[nextValueIndex] = val1; + lastValues2[nextValueIndex] = val2; + + nextValueIndex++; + nextValueIndex %= maxLastValuesCount; + lastValuesCount++; + if (lastValuesCount > maxLastValuesCount) + { + lastValuesCount = maxLastValuesCount; + } + + shift += shiftStep; + shift %= squareWidth; + Invalidate(); + } + + public int Maximum + { + get + { + return max; + } + + set + { + // Make sure that the maximum value is never set lower than the minimum value. + if (value < min) + { + min = value; + } + + max = value; + + // Invalidate the control to get a repaint. + this.Invalidate(); + } + } + + } +} diff --git a/UsageControl/UsageHistoryControl.resx b/UsageControl/UsageHistoryControl.resx new file mode 100644 index 0000000..487ccbc --- /dev/null +++ b/UsageControl/UsageHistoryControl.resx @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + True + + + True + + + UsageHistoryControl + + + 80 + + + (Default) + + + False + + + Private + + + 8, 8 + + \ No newline at end of file