Initial revision

rg2023 [2002-09-29 20:59:06]
Initial revision
Filename
csharpsrc/AsyncCore/AssemblyInfo.cs
csharpsrc/AsyncCore/AsyncCore.csproj
csharpsrc/AsyncCore/AsyncCore.csproj.user
csharpsrc/AsyncCore/AsyncCore.sln
csharpsrc/AsyncCore/AsyncCore.suo
csharpsrc/AsyncCore/TPTask.cs
csharpsrc/AsyncCore/TPTaskCompleteEventArgs.cs
csharpsrc/AsyncCore/TaskPool.cs
csharpsrc/AsyncCore/TaskProxy.cs
csharpsrc/AsyncCore/TaskRequest.cs
csharpsrc/AsyncCoreUnittest/App.ico
csharpsrc/AsyncCoreUnittest/AssemblyInfo.cs
csharpsrc/AsyncCoreUnittest/AsyncCoreUnittest.csproj
csharpsrc/AsyncCoreUnittest/AsyncCoreUnittest.csproj.user
csharpsrc/AsyncCoreUnittest/Class1.cs
csharpsrc/AsyncCoreUnittest/WeatherRetriever.cs
diff --git a/csharpsrc/AsyncCore/AssemblyInfo.cs b/csharpsrc/AsyncCore/AssemblyInfo.cs
new file mode 100644
index 0000000..256e25f
--- /dev/null
+++ b/csharpsrc/AsyncCore/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("AsyncCore")]
+[assembly: AssemblyDescription("Core classes used for Asynchronous processing")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Programming Systems Lab, Columbia University")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("Programming Systems Lab, Columbia University")]
+[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.1002.25010")]
+
+//
+// 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\<configuration>. 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/csharpsrc/AsyncCore/AsyncCore.csproj b/csharpsrc/AsyncCore/AsyncCore.csproj
new file mode 100644
index 0000000..9f87238
--- /dev/null
+++ b/csharpsrc/AsyncCore/AsyncCore.csproj
@@ -0,0 +1,113 @@
+<VisualStudioProject>
+    <CSHARP
+        ProjectType = "Local"
+        ProductVersion = "7.0.9466"
+        SchemaVersion = "1.0"
+        ProjectGuid = "{1E8BA3A8-8D8A-4802-97D0-DAA0425AA45B}"
+    >
+        <Build>
+            <Settings
+                ApplicationIcon = ""
+                AssemblyKeyContainerName = ""
+                AssemblyName = "AsyncCore"
+                AssemblyOriginatorKeyFile = ""
+                DefaultClientScript = "JScript"
+                DefaultHTMLPageLayout = "Grid"
+                DefaultTargetSchema = "IE50"
+                DelaySign = "false"
+                OutputType = "Library"
+                RootNamespace = "PSL.AsyncCore"
+                StartupObject = ""
+            >
+                <Config
+                    Name = "Debug"
+                    AllowUnsafeBlocks = "false"
+                    BaseAddress = "285212672"
+                    CheckForOverflowUnderflow = "false"
+                    ConfigurationOverrideFile = ""
+                    DefineConstants = "DEBUG;TRACE"
+                    DocumentationFile = ""
+                    DebugSymbols = "true"
+                    FileAlignment = "4096"
+                    IncrementalBuild = "true"
+                    Optimize = "false"
+                    OutputPath = "..\..\bin\"
+                    RegisterForComInterop = "false"
+                    RemoveIntegerChecks = "false"
+                    TreatWarningsAsErrors = "false"
+                    WarningLevel = "4"
+                />
+                <Config
+                    Name = "Release"
+                    AllowUnsafeBlocks = "false"
+                    BaseAddress = "285212672"
+                    CheckForOverflowUnderflow = "false"
+                    ConfigurationOverrideFile = ""
+                    DefineConstants = "TRACE"
+                    DocumentationFile = ""
+                    DebugSymbols = "false"
+                    FileAlignment = "4096"
+                    IncrementalBuild = "false"
+                    Optimize = "true"
+                    OutputPath = "bin\Release\"
+                    RegisterForComInterop = "false"
+                    RemoveIntegerChecks = "false"
+                    TreatWarningsAsErrors = "false"
+                    WarningLevel = "4"
+                />
+            </Settings>
+            <References>
+                <Reference
+                    Name = "System"
+                    AssemblyName = "System"
+                    HintPath = "..\..\..\..\..\WINDOWS\Microsoft.NET\Framework\v1.0.3705\System.dll"
+                />
+                <Reference
+                    Name = "System.Data"
+                    AssemblyName = "System.Data"
+                    HintPath = "..\..\..\..\..\WINDOWS\Microsoft.NET\Framework\v1.0.3705\System.Data.dll"
+                />
+                <Reference
+                    Name = "System.XML"
+                    AssemblyName = "System.Xml"
+                    HintPath = "..\..\..\..\..\WINDOWS\Microsoft.NET\Framework\v1.0.3705\System.XML.dll"
+                />
+            </References>
+        </Build>
+        <Files>
+            <Include>
+                <File
+                    RelPath = "AssemblyInfo.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "TaskPool.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "TaskProxy.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "TaskRequest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "TPTask.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "TPTaskCompleteEventArgs.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+            </Include>
+        </Files>
+    </CSHARP>
+</VisualStudioProject>
+
diff --git a/csharpsrc/AsyncCore/AsyncCore.csproj.user b/csharpsrc/AsyncCore/AsyncCore.csproj.user
new file mode 100644
index 0000000..4089d2b
--- /dev/null
+++ b/csharpsrc/AsyncCore/AsyncCore.csproj.user
@@ -0,0 +1,48 @@
+<VisualStudioProject>
+    <CSHARP>
+        <Build>
+            <Settings ReferencePath = "" >
+                <Config
+                    Name = "Debug"
+                    EnableASPDebugging = "false"
+                    EnableASPXDebugging = "false"
+                    EnableUnmanagedDebugging = "false"
+                    EnableSQLServerDebugging = "false"
+                    RemoteDebugEnabled = "false"
+                    RemoteDebugMachine = ""
+                    StartAction = "Project"
+                    StartArguments = ""
+                    StartPage = ""
+                    StartProgram = ""
+                    StartURL = ""
+                    StartWorkingDirectory = ""
+                    StartWithIE = "false"
+                />
+                <Config
+                    Name = "Release"
+                    EnableASPDebugging = "false"
+                    EnableASPXDebugging = "false"
+                    EnableUnmanagedDebugging = "false"
+                    EnableSQLServerDebugging = "false"
+                    RemoteDebugEnabled = "false"
+                    RemoteDebugMachine = ""
+                    StartAction = "Project"
+                    StartArguments = ""
+                    StartPage = ""
+                    StartProgram = ""
+                    StartURL = ""
+                    StartWorkingDirectory = ""
+                    StartWithIE = "false"
+                />
+            </Settings>
+        </Build>
+        <OtherProjectSettings
+            CopyProjectDestinationFolder = ""
+            CopyProjectUncPath = ""
+            CopyProjectOption = "0"
+            ProjectView = "ProjectFiles"
+            ProjectTrust = "0"
+        />
+    </CSHARP>
+</VisualStudioProject>
+
diff --git a/csharpsrc/AsyncCore/AsyncCore.sln b/csharpsrc/AsyncCore/AsyncCore.sln
new file mode 100644
index 0000000..ffc458c
--- /dev/null
+++ b/csharpsrc/AsyncCore/AsyncCore.sln
@@ -0,0 +1,27 @@
+Microsoft Visual Studio Solution File, Format Version 7.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncCore", "AsyncCore.csproj", "{1E8BA3A8-8D8A-4802-97D0-DAA0425AA45B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncCoreUnittest", "..\AsyncCoreUnittest\AsyncCoreUnittest.csproj", "{B11691E6-B627-4DBF-869E-56CBDA119BBC}"
+EndProject
+Global
+	GlobalSection(SolutionConfiguration) = preSolution
+		ConfigName.0 = Debug
+		ConfigName.1 = Release
+	EndGlobalSection
+	GlobalSection(ProjectDependencies) = postSolution
+	EndGlobalSection
+	GlobalSection(ProjectConfiguration) = postSolution
+		{1E8BA3A8-8D8A-4802-97D0-DAA0425AA45B}.Debug.ActiveCfg = Debug|.NET
+		{1E8BA3A8-8D8A-4802-97D0-DAA0425AA45B}.Debug.Build.0 = Debug|.NET
+		{1E8BA3A8-8D8A-4802-97D0-DAA0425AA45B}.Release.ActiveCfg = Release|.NET
+		{1E8BA3A8-8D8A-4802-97D0-DAA0425AA45B}.Release.Build.0 = Release|.NET
+		{B11691E6-B627-4DBF-869E-56CBDA119BBC}.Debug.ActiveCfg = Debug|.NET
+		{B11691E6-B627-4DBF-869E-56CBDA119BBC}.Debug.Build.0 = Debug|.NET
+		{B11691E6-B627-4DBF-869E-56CBDA119BBC}.Release.ActiveCfg = Release|.NET
+		{B11691E6-B627-4DBF-869E-56CBDA119BBC}.Release.Build.0 = Release|.NET
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+	EndGlobalSection
+	GlobalSection(ExtensibilityAddIns) = postSolution
+	EndGlobalSection
+EndGlobal
diff --git a/csharpsrc/AsyncCore/AsyncCore.suo b/csharpsrc/AsyncCore/AsyncCore.suo
new file mode 100644
index 0000000..9fde725
Binary files /dev/null and b/csharpsrc/AsyncCore/AsyncCore.suo differ
diff --git a/csharpsrc/AsyncCore/TPTask.cs b/csharpsrc/AsyncCore/TPTask.cs
new file mode 100644
index 0000000..a68c5c6
--- /dev/null
+++ b/csharpsrc/AsyncCore/TPTask.cs
@@ -0,0 +1,248 @@
+using System;
+using System.Collections;
+using System.Threading;
+
+namespace PSL.AsyncCore
+{
+	/// <summary>
+	/// Internal class used by the TaskPool for initialization.
+	/// Encapsulates a TPTask and its associated thread.
+	/// </summary>
+	internal sealed class TPTaskInit
+	{
+		private TPTask m_task = null;
+		private Thread m_worker = null;
+
+		/// <summary>
+		/// Ctor
+		/// </summary>
+		internal TPTaskInit()
+		{}
+
+		/// <summary>
+		/// Property gets/sets the TPTask instance
+		/// </summary>
+		internal TPTask Task
+		{
+			get
+			{ return m_task; }
+			set
+			{ m_task = value; }
+		}
+
+		/// <summary>
+		/// Property gets/sets the thread associated with a TPTask
+		/// </summary>
+		internal Thread Worker
+		{
+			get
+			{ return m_worker; }
+			set
+			{ m_worker = value; }
+		}
+	}//End-TPTaskInit
+
+
+	/// <summary>
+	/// A TPTask forces a thread to follow the leaders-followers design pattern
+	/// </summary>
+	internal sealed class TPTask
+	{
+		/// <summary>
+		/// TaskCompletion event handler delegate
+		/// </summary>
+		public delegate void TPTaskCompleteHandler( TPTaskCompleteEventArgs tceArg );
+
+		/// <summary>
+		/// TaskCompletion event
+		/// </summary>
+		public static event TPTaskCompleteHandler TPTaskComplete;
+
+		// Data used by each TPTask
+
+		/// <summary>
+		/// Condition variable used to synchronize TPTasks running in the TaskPool
+		/// </summary>
+		private AutoResetEvent m_condition = null;
+
+		/// <summary>
+		/// After a thread becomes the leader, if there are no
+		/// pending requests the leader will exit the TaskPool
+		/// in some cases we may want to keep the thread in the TaskPool
+		/// until the TaskPool is explicitly shutdown
+		/// </summary>
+		private bool m_bRecycleTask = false;
+
+		private Guid m_taskID = Guid.Empty;
+
+		// Data shared by all TPTasks
+
+		/// <summary>
+		/// Indicates whether a leader TPTask thread exists in this process.
+		/// </summary>
+		private static bool bLeaderAvailable = false;
+
+		/// <summary>
+		/// Returns the number of TPTask threads running in this process.
+		/// </summary>
+		private static int nNumTasks = 0;
+
+		/// <summary>
+		/// Queue used to hold TaskRequests to service
+		/// <seealso cref="TaskRequest"/>
+		/// </summary>
+		internal static Queue requestQ = new Queue();
+
+		/// <summary>
+		/// Ctor
+		/// Construct TPTask passing in a shared condition used to signal threads
+		/// </summary>
+		/// <param name="condition">Condition variable shared by all TPTasks in
+		/// a TaskPool</param>
+		public TPTask( ref AutoResetEvent condition )
+		{
+			m_condition = condition;
+		}
+
+		/// <summary>
+		/// Property gets/sets the TPTasks recyclable property.
+		/// A recyclable TPTask thread will remain in the TaskPool
+		/// after servicing a TaskRequest.
+		/// </summary>
+		public bool RecycleTask
+		{
+			get
+			{ return m_bRecycleTask; }
+
+			set
+			{ m_bRecycleTask = value; }
+		}
+
+		/// <summary>
+		/// Property gets/sets the TaskID of a TPTask, set when a TPTask
+		/// is servicing a TaskRequest. The TPTask assumes the TaskID of the
+		/// TaskRequest it is servicing.
+		/// </summary>
+		public Guid TaskID
+		{
+			get
+			{ return m_taskID; }
+			set
+			{ m_taskID = value; }
+		}
+
+		/// <summary>
+		/// Property gets the number of TaskRequests queued for servicing
+		/// </summary>
+		public static int QueuedRequests
+		{
+			get
+			{ return requestQ.Count; }
+		}
+
+		/// <summary>
+		/// Property returns the number of TPTask threads running
+		/// </summary>
+		public static int Tasks
+		{
+			get
+			{ return nNumTasks; }
+		}
+
+		/// <summary>
+		/// Procedure enforces the leader-followers behavior of TPTask threads
+		/// </summary>
+		/// <remarks></remarks>
+		public void Service()
+		{
+			// Every thread that comes thru here ups the thread count
+			Interlocked.Increment( ref nNumTasks );
+
+			while( true )
+			{
+				// Begin critical section
+				Monitor.Enter( this );
+				// While a leader has been selected...wait around
+				while( bLeaderAvailable )
+				{
+					m_condition.WaitOne();
+				}
+
+				// Assert self as leader before leaving critical section
+				bLeaderAvailable = true;
+				// Leave critical section
+				Monitor.Exit( this );
+
+				bool bExitLoop = false;
+
+				// Nothing else to do so this thread can exit or be recycled
+				if( requestQ.Count == 0 )
+					bExitLoop = true;
+
+				// Begin critical section
+				Monitor.Enter( this );
+
+				// Signal self is no longer the leader
+				bLeaderAvailable = false;
+
+				Monitor.Exit( this );
+
+				// Signal a follower to become the new leader
+				m_condition.Set();
+
+				if( bExitLoop )
+				{
+					// If this task is not marked as recyclable
+					// then it must exit if no requests are
+					// pending
+					if( !m_bRecycleTask )
+					{
+						// Thread on its way out so decrease thread count
+						Interlocked.Decrement( ref nNumTasks );
+						break;
+					}
+				}
+				else
+				{
+					try
+					{
+						// Dequeue Service request
+						TaskRequest req = (TaskRequest) requestQ.Dequeue();
+						// Set taskID
+						this.TaskID = req.TaskID;
+						// Execute Callback
+						object objRes = req.TaskCb( req.Context );
+						// If any one subscribed, then fire LFTaskDone event
+						if( TPTaskComplete != null )
+						{
+							TPTaskCompleteEventArgs tceArg = new TPTaskCompleteEventArgs();
+							tceArg.TaskID = req.TaskID;
+							tceArg.Result = objRes;
+							TPTaskComplete( tceArg );
+						}
+					}
+					catch( Exception e )// Catch any exceptions thrown
+					{
+						// If any one subscribed, then fire LFTaskFailed event
+						if( TPTaskComplete != null )
+						{
+							TPTaskCompleteEventArgs tceArg = new TPTaskCompleteEventArgs();
+							tceArg.TaskID = this.TaskID;
+							// Signal that we had errors so that
+							// clients can check the
+							// ExceptionMessage property
+							tceArg.HasErrors = true;
+							tceArg.Result = e.Message;
+							TPTaskComplete( tceArg );
+						}
+					}
+					finally
+					{
+						// Reset task ID
+						this.TaskID = Guid.Empty;
+					}
+				}
+			}// End-while(true)
+		}// End-Method Service()
+	}
+}
diff --git a/csharpsrc/AsyncCore/TPTaskCompleteEventArgs.cs b/csharpsrc/AsyncCore/TPTaskCompleteEventArgs.cs
new file mode 100644
index 0000000..1ca38fc
--- /dev/null
+++ b/csharpsrc/AsyncCore/TPTaskCompleteEventArgs.cs
@@ -0,0 +1,67 @@
+using System;
+
+namespace PSL.AsyncCore
+{
+	/// <summary>
+	/// TPTaskCompleteEventArgs - one of these classes is created each time a
+	/// TPTask thread finishes servicing a TaskRequest (invoking its TaskCallback)
+	/// <seealso cref="TaskRequest"/>
+	/// <seealso cref="TaskPool"/>
+	/// </summary>
+	public sealed class TPTaskCompleteEventArgs:EventArgs
+	{
+		// May be object or string (exception message)
+		private object m_objResult = null;
+		private bool m_bErrorsOccurred = false;
+		private Guid m_taskID = Guid.Empty;
+
+		/// <summary>
+		/// Ctor
+		/// </summary>
+		public TPTaskCompleteEventArgs()
+		{
+		}
+
+		/// <summary>
+		/// Property indicates whether errors occurred when the TaskRequest
+		/// was being serviced.
+		/// </summary>
+		public bool HasErrors
+		{
+			get
+			{ return m_bErrorsOccurred; }
+			set
+			{ m_bErrorsOccurred = true; }
+		}
+
+		public object Result
+		{
+			get
+			{ return m_objResult; }
+			set
+			{ m_objResult = value; }
+		}
+
+		public Guid TaskID
+		{
+			get
+			{ return m_taskID; }
+			set
+			{ m_taskID = value; }
+		}
+
+		// Cannot be set
+		public string ExceptionMessage
+		{
+			get
+			{
+				if( !m_bErrorsOccurred )
+					return "";
+
+				if( m_objResult == null )
+					return "";
+				else return (string) m_objResult;
+			}
+		}
+	}
+}
diff --git a/csharpsrc/AsyncCore/TaskPool.cs b/csharpsrc/AsyncCore/TaskPool.cs
new file mode 100644
index 0000000..b405657
--- /dev/null
+++ b/csharpsrc/AsyncCore/TaskPool.cs
@@ -0,0 +1,263 @@
+using System;
+using System.Threading;
+using System.Collections;
+using System.Diagnostics;
+
+namespace PSL.AsyncCore
+{
+	/// <summary>
+	/// A TaskPool is a threadpool implemented using the leaders-followers
+	/// design pattern. The TaskPool is an event based entity, clients of the
+	/// TaskPool send task requests and get notified on task completion.
+	/// </summary>
+	public sealed class TaskPool
+	{
+		/// <summary>
+		/// Minimum number of TPTask threads running in the TaskPool
+		/// on initialization
+		/// </summary>
+		/// <remarks>There is currently no TaskPool method to increase
+		/// or decrease the number of TPTask threads in the TaskPool</remarks>
+		public const int MIN_ACTIVE_TASKS = 4;
+
+		/// <summary>
+		/// Maximum number of TPTask threads that can be running in the
+		/// TaskPool
+		/// </summary>
+		public const int MAX_ACTIVE_TASKS = 10;
+
+		/// <summary>
+		/// Max number of milliseconds to wait for a thread to finish
+		/// at TaskPool shutdown before we kill it
+		/// set at max RPC timeout of 2 mins = 120 secs
+		/// </summary>
+		public const int MAX_THREAD_WAIT = 120000;
+
+		/// <summary>
+		/// Links TaskIDs to client notification callbacks
+		/// For every Task request we store the callback provided by the
+		/// client so they can be notified on completion
+		/// </summary>
+		private static Hashtable ClientNotification = new Hashtable();
+
+		/// <summary>
+		///	Stores the details of the TPTasks threads in the TaskPool
+		/// </summary>
+		private static ArrayList lstTasks = new ArrayList();
+
+		/// <summary>
+		/// Condition variable used to synchronize TPTask threads running in
+		/// the TaskPool and ensure they abide by the leaders-followers model
+		/// </summary>
+		private static AutoResetEvent condition = new AutoResetEvent( true );
+
+		/// <summary>
+		/// Indicates whether the TaskPool has been initialized or not
+		/// </summary>
+		private static bool bInitialized = false;
+
+		/// <summary>
+		/// Indicates whether the TaskPool is in the process of shutting down
+		/// </summary>
+		private static bool bShuttingDown = false;
+
+		/// <summary>
+		/// Never instantiate this class, there should be one TaskPool per
+		/// process
+		/// </summary>
+		private TaskPool()
+		{
+		}
+
+		/// <summary>
+		/// Property indicating the number of active TPTask threads in the
+		/// TaskPool
+		/// </summary>
+		public static int Threads
+		{
+			get
+			{return TPTask.Tasks; }
+		}
+
+		/// <summary>
+		/// Property indicating whether the TaskPool has been initialized
+		/// </summary>
+		public static bool IsInitialized
+		{
+			get
+			{ return bInitialized; }
+		}
+
+		/// <summary>
+		/// Property indicating whether the TaskPool is shutting down
+		/// </summary>
+		public static bool IsShuttingDown
+		{
+			get
+			{ return bShuttingDown; }
+		}
+
+
+		/// <summary>
+		/// TaskPool subscribes to the TPTaskComplete event. When a TPTask
+		/// thread is finished servicing a request, locate the
+		/// subscription of the client that queued the request and
+		/// notify them via the callback they provided as part of
+		/// their subscription.
+		/// <seealso cref="TPTask"/>
+		/// <seealso cref="TPTaskCompleteEventArgs"/>
+		/// </summary>
+		/// <param name="tceArg">TPTask completion event arguement</param>
+		private static void OnTPTaskComplete( TPTaskCompleteEventArgs tceArg )
+		{
+			lock( ClientNotification.SyncRoot )
+			{
+				if( ClientNotification.ContainsKey( tceArg.TaskID ) )
+				{
+					TaskRequest.NotificationCallback objNotify = (TaskRequest.NotificationCallback) ClientNotification[tceArg.TaskID];
+					// Notifiy specific client
+					objNotify( tceArg );
+					// Remove subscription
+					ClientNotification.Remove( tceArg.TaskID );
+				}
+			}
+		}
+
+		/// <summary>
+		/// Function queues a TaskRequest to the TaskPool
+		/// </summary>
+		/// <param name="req">The TaskRequest to queue</param>
+		/// <returns>A Guid used as a TaskID - if the TaskPool is in the process of
+		/// Shutting down then Guid.Empty is returned</returns>
+		/// <exception cref="ArgumentNullException">Thrown if TaskRequest
+		/// is null or not valid</exception>
+		public static Guid QueueTask( TaskRequest req )
+		{
+			if( req == null || !req.IsValid )
+				throw new ArgumentNullException( "TaskRequest is null or contains null valued callbacks" );
+
+			// If taskpool is shutting down then return Guid.Empty
+			if( bShuttingDown )
+				return Guid.Empty;
+
+			if( !bInitialized )
+				Initialize();
+
+			Guid taskID = Guid.Empty;
+
+			lock( TPTask.requestQ.SyncRoot )
+			{
+				// Generate Guid - task id if none exists
+				if( req.TaskID == Guid.Empty )
+					req.TaskID = Guid.NewGuid();
+
+				taskID = req.TaskID;
+
+				// Add request item to TPTask requestQ
+				TPTask.requestQ.Enqueue( req );
+			}// End-lock on TPTask.requestQ
+
+			// Tag taskID to notification callback
+			lock( ClientNotification.SyncRoot )
+			{
+				if( taskID != Guid.Empty )
+					ClientNotification.Add( taskID, req.NotifyCb );
+			}// End-lock on ClientNotification
+
+			// Return taskID to client
+			return taskID;
+		}
+
+		// Get Taskpool started by kicking off some tasks
+		/// <summary>
+		/// Procedure gets the TaskPool started by kicking off a few
+		/// TPTask threads.
+		/// </summary>
+		public static void Initialize()
+		{
+			if( TPTask.Tasks != 0 )
+				return;
+
+			lock( lstTasks.SyncRoot )
+			{
+				// If shutting down then exit
+				if( bShuttingDown )
+					return;
+
+				// If already initialized then exit
+				if( bInitialized )
+					return;
+
+				// Hook up event handlers and flag initialized
+				if( !bInitialized )
+				{
+					TPTask.TPTaskComplete += new TPTask.TPTaskCompleteHandler( OnTPTaskComplete );
+					bInitialized = true;
+				}
+
+				for( int i = 0; i < MIN_ACTIVE_TASKS; i++ )
+				{
+					TPTaskInit init = new TPTaskInit();
+					// Create new TPTask
+					init.Task = new TPTask( ref condition );
+					// Keep thread in ThreadPool
+					init.Task.RecycleTask = true;
+					// Create worker thread
+					init.Worker = new Thread( new ThreadStart( init.Task.Service ) );
+					// Start worker
+					init.Worker.Start();
+					// Add this task to our list of tasks
+					lstTasks.Add( init );
+				}
+			}//End-Lock on lstTasks
+		}
+
+		/// <summary>
+		/// Procedure shuts down the TaskPool by allowing the TPTask threads
+		/// running to finish what they are doing and exit the TaskPool(up to MAX_THREAD_WAIT)
+		/// before aborting each one.
+		/// </summary>
+		/// <remarks>To shutdown the TaskPool, iterate thru the list of
+		/// TPTasks and set their RecycleTask property to false,
+		/// so they terminate after they finish processing their current
+		/// request and wait (up to MAX_THREAD_WAIT) on each TPTask thread
+		/// to exit before aborting the TPTask thread.
+		/// </remarks>
+		public static void Shutdown()
+		{
+			lock( lstTasks.SyncRoot )
+			{
+				// Signal that we are shutting down
+				bShuttingDown = true;
+
+				for( int i = 0; i < lstTasks.Count; i++ )
+				{
+					try
+					{
+						TPTaskInit init = (TPTaskInit) lstTasks[i];
+						// Do not recycle task
+						init.Task.RecycleTask = false;
+						// Wait on thread to exit.
+						// Instead of waiting indefinitely
+						// we may have have to interrupt it and
+						// catch the exception
+						init.Worker.Join( MAX_THREAD_WAIT );
+						if( init.Worker.IsAlive )
+							init.Worker.Abort();
+					}
+					catch( Exception e )
+					{
+						string strMsg = e.Message;
+					}
+				}
+
+				// Clear all tasks
+				lstTasks.Clear();
+				// Clear all client subscriptions
+				ClientNotification.Clear();
+				// Set threadpool as uninitialized
+				bInitialized = false;
+			}//End-lock lstTasks
+		}//End-Shutdown
+	}//End-TaskPool
+}//End-namespace PSL.AsyncCore
diff --git a/csharpsrc/AsyncCore/TaskProxy.cs b/csharpsrc/AsyncCore/TaskProxy.cs
new file mode 100644
index 0000000..31eb0da
--- /dev/null
+++ b/csharpsrc/AsyncCore/TaskProxy.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Threading;
+using System.Collections;
+
+namespace PSL.AsyncCore
+{
+	/// <summary>
+	/// Status enumeration of TaskProxies
+	/// </summary>
+	public enum enuTaskProxyStatus
+	{
+		AcceptingRequests,
+		WaitingOnResults,
+		ResultsReady
+	};
+
+	/// <summary>
+	/// A TaskProxy will accept a set of TaskRequests,
+	/// Queue them in the order received to the TaskPool and keep a hashtable
+	/// indexed by the TaskIDs returned from the TaskPool queue operation.
+	/// The TaskProxy will provide a callback to be notified on
+	/// when a task request is serviced in the TaskPool.
+	/// The TaskProxy is created with a condition variable with which
+	/// it will signal when all results are available.
+	/// Once a TaskProxy starts queuing requests, no more TaskRequests can
+	/// be accepted
+	/// </summary>
+	public sealed class TaskProxy
+	{
+		/// <summary>
+		/// Condition variable used to reference client provided
+		/// conditon variable
+		/// </summary>
+		private AutoResetEvent m_condition = null;
+
+		/// <summary>
+		/// Queue of TaskReqests
+		/// </summary>
+		private Queue m_requestQ = new Queue();
+
+		/// <summary>
+		/// Hashtable of results indexed by TaskID
+		/// </summary>
+		private Hashtable m_results = new Hashtable();
+
+		/// <summary>
+		/// Number of tasks the TaskProxy has to wait on
+		/// </summary>
+		private int m_nTasksPending = 0;
+
+		/// <summary>
+		/// TaskProxy status, initialzed to enuTaskProxyStatus.AcceptingRequests;
+		/// </summary>
+		private enuTaskProxyStatus m_status = enuTaskProxyStatus.AcceptingRequests;
+
+		/// <summary>
+		/// Ctor
+		/// </summary>
+		/// <param name="condition">Reference to a conditon variable
+		/// the TaskProxy will signal on when all TaskRequests queued
+		/// are ready.</param>
+		public TaskProxy( ref AutoResetEvent condition )
+		{
+			// Set condition to non-signaled
+			condition.Reset();
+			m_condition = condition;
+		}
+
+		/// <summary>
+		/// Property gets the number of TasksPending (number of tasks the
+		/// TaskProxy has to wait on)
+		/// </summary>
+		public int TasksPending
+		{
+			get
+			{ return m_nTasksPending; }
+		}
+
+		// Do we really want to do this? - Support multiple callers on a
+		// single proxy - if so we may want to add an event to notify clients
+		// when a proxy is being reset so that it passes what data it has
+		// in the reset event
+		public AutoResetEvent Condition
+		{
+			get
+			{ return m_condition; }
+		}
+
+		/// <summary>
+		/// Procedure represents the notification callback provided by the
+		/// TaskProxy. This callback is invoked whenever a task the TaskProxy
+		/// is waiting on has been serviced by the TaskPool. When all the tasks the
+		/// proxy is waiting on have been serviced it will signal external clients
+		/// using the condition variable reference passed from the client at
+		/// TaskProxy construction.
+		/// </summary>
+		/// <param name="tceArg"></param>
+		private void OnTPTaskComplete( TPTaskCompleteEventArgs tceArg )
+		{
+			lock( m_results.SyncRoot )
+			{
+				// Index results by TaskID
+				m_results.Add( tceArg.TaskID, tceArg );
+				m_nTasksPending--;
+
+				if( m_nTasksPending == 0 )
+				{
+					// Indicate results are ready by changing state
+					m_status = enuTaskProxyStatus.ResultsReady;
+					// Signal to any waiting client
+					m_condition.Set();
+				}
+			}
+		}
+
+		/// <summary>
+		/// Function adds to the list of TaskRequests a TaskProxy will
+		/// wait on.
+		/// </summary>
+		/// <param name="req">The TaskRequest to wait on</param>
+		/// <returns>The Guid (TaskID) associated with the request.
+		/// This TaskID is used to query for results once the
+		/// TaskProxy signals the client that all task requests have
+		/// been serviced.</returns>
+		/// <remarks>Note: The TaskRequest queued may or may not have set
+		/// a notification callback, if one has been set the TaskProxy will
+		/// REPLACE it with its own notification callback.</remarks>
+		/// <exception cref="ArgumentNullException">Thrown if a null
+		/// TaskRequest is added</exception>
+		public Guid AddTaskRequest( TaskRequest req )
+		{
+			if( req == null )
+				throw new ArgumentNullException( "TaskRequest is null" );
+
+			// Assign taskID if we have to
+			if( req.TaskID == Guid.Empty )
+				req.TaskID = Guid.NewGuid();
+
+			Guid taskID = req.TaskID;
+
+			// Enqueue task
+			lock( m_requestQ.SyncRoot )
+			{
+				if( m_status != enuTaskProxyStatus.AcceptingRequests )
+					throw new InvalidOperationException( "Proxy is no longer accepting requests - Check TaskProxy status" );
+
+				// Link notify callback to TaskProxy
+				// note use of = instead of +=
+				// single TaskProxy is sole subscriber
+				// to this request being serviced
+				req.NotifyCb = new TaskRequest.NotificationCallback( this.OnTPTaskComplete );
+				m_requestQ.Enqueue( req );
+				m_nTasksPending++;
+			}
+
+			return taskID;
+		}
+
+		/// <summary>
+		/// Procedure resets the state of a TaskProxy.
+		/// Clears: The request queue, the results table, and the TaskProxy
+		/// state.
+		/// </summary>
+		public void Reset()
+		{
+			lock( this )
+			{
+				m_requestQ.Clear();
+				m_results.Clear();
+				m_status = enuTaskProxyStatus.AcceptingRequests;
+				m_condition.Reset();
+			}
+		}
+
+		/// <summary>
+		/// Procedure begins the TaskProxy wait operation.
+		/// TaskProxy will send all queued tasks to the TaskPool for
+		/// servicing and change its state to enuTaskProxyStatus.WaitingOnResults.
+		/// Once the state change takes place the TaskProxy will no longer accept
+		/// task requests hence calling AddTaskRequest after this state change will
+		/// result in an InvalidOperationException being thrown.
+		/// </summary>
+		public void WaitOnTasks()
+		{
+			lock( m_requestQ.SyncRoot )
+			{
+				// If we are not about to transition from AcceptingRequests
+				// then we have nothing to do here, so exit
+				if( m_status != enuTaskProxyStatus.AcceptingRequests )
+					return;
+
+				// Pass all the requests we have to the TaskPool
+				// do not empty the requestQ, we need to preserve the ordering
+				// just in case external clients don't
+				IEnumerator it = m_requestQ.GetEnumerator();
+				while( it.MoveNext() )
+				{
+					TaskPool.QueueTask( (TaskRequest) it.Current );
+				}
+
+				// Once Wait is called, Proxy no longer accepts new requests
+				// we indicate this by changing state
+				m_status = enuTaskProxyStatus.WaitingOnResults;
+			}
+		}
+
+		/// <summary>
+		/// Function allows clients of a TaskProxy to query for the results
+		/// of a specific TaskID.
+		/// </summary>
+		/// <param name="taskID">TaskID associated with a TaskRequest that
+		/// was queued for servicing</param>
+		/// <returns>The TaskCompleteEventArgs representing the results
+		/// of servicing.</returns>
+		public TPTaskCompleteEventArgs QueryResult( Guid taskID )
+		{
+			if( m_status != enuTaskProxyStatus.ResultsReady )
+				throw new InvalidOperationException( "Cannot query TaskProxy for results until results ready - check status first or wait on condition variable for signal" );
+
+			lock( m_results.SyncRoot )
+			{
+				if( m_results.ContainsKey( taskID ) )
+				{
+					return (TPTaskCompleteEventArgs) m_results[taskID];
+				}
+			}
+			return null;
+		}
+
+	}
+}
diff --git a/csharpsrc/AsyncCore/TaskRequest.cs b/csharpsrc/AsyncCore/TaskRequest.cs
new file mode 100644
index 0000000..483b463
--- /dev/null
+++ b/csharpsrc/AsyncCore/TaskRequest.cs
@@ -0,0 +1,160 @@
+using System;
+
+namespace PSL.AsyncCore
+{
+	/// <summary>
+	/// A TaskRequest encapsulates a function call to make (delegate to invoke)
+	/// and a notification callback which will be invoked after the delegate is
+	/// invoked with the results of the delegate invocation.
+	/// </summary>
+	public sealed class TaskRequest
+	{
+		/// <summary>
+		/// Notification callback
+		/// </summary>
+		public delegate void NotificationCallback( TPTaskCompleteEventArgs tceArg );
+
+		/// <summary>
+		/// Delegate wrapping the function to be called
+		/// </summary>
+		public delegate object TaskCallback( object objCtx );
+
+		/// <summary>
+		/// Task callback (delegate) instance
+		/// </summary>
+		private TaskCallback m_objTaskCb = null;
+
+		/// <summary>
+		/// TaskID of this request - usually set by client
+		/// </summary>
+		private Guid m_taskID = Guid.Empty;
+
+		/// <summary>
+		/// Notification callback (delegate) instance
+		/// </summary>
+		private NotificationCallback m_objNotifyCb = null;
+
+		/// <summary>
+		/// Context object instance passed to TaskCallback instance and
+		/// used when the TaskCallback is invoked.
+		/// </summary>
+		private object m_objCtx = null;
+
+		/// <summary>
+		/// Ctor
+		/// </summary>
+		public TaskRequest()
+		{
+		}
+
+		/// <summary>
+		/// Ctor
+		/// </summary>
+		/// <param name="objTaskCb">TaskCallback to be used in this TaskRequest</param>
+		public TaskRequest( TaskCallback objTaskCb )
+		{
+			m_objTaskCb = objTaskCb;
+		}
+
+		/// <summary>
+		/// Ctor
+		/// </summary>
+		/// <param name="objTaskCb">TaskCallback to be used in this TaskRequest</param>
+		/// <param name="objCtx">Context object used with the TaskCallback</param>
+		public TaskRequest( TaskCallback objTaskCb, object objCtx )
+		{
+			m_objTaskCb = objTaskCb;
+			m_objCtx = objCtx;
+		}
+
+		/// <summary>
+		/// Ctor
+		/// </summary>
+		/// <param name="objTaskCb">TaskCallback to be used in this TaskRequest</param>
+		/// <param name="objNotifyCb">Notification callback which will be invoked
+		/// after the TaskCallback is invoked</param>
+		public TaskRequest( TaskCallback objTaskCb, NotificationCallback objNotifyCb )
+		{
+			m_objTaskCb = objTaskCb;
+			m_objNotifyCb = objNotifyCb;
+		}
+
+		/// <summary>
+		/// Ctor
+		/// </summary>
+		/// <param name="objTaskCb">TaskCallback to be used in this TaskRequest</param>
+		/// <param name="objNotifyCb">Notification callback which will be invoked
+		/// after the TaskCallback is invoked</param>
+		/// <param name="objCtx">Context object used with the TaskCallback</param>
+		public TaskRequest( TaskCallback objTaskCb, NotificationCallback objNotifyCb, object objCtx )
+		{
+			m_objTaskCb = objTaskCb;
+			m_objNotifyCb = objNotifyCb;
+			m_objCtx = objCtx;
+		}
+
+		/// <summary>
+		/// Property indicates whether a TaskRequest is valid. A TaskRequest needs
+		/// to have a TaskCallback and a Notification Callback to be considered
+		/// valid
+		/// </summary>
+		public bool IsValid
+		{
+			get
+			{ return m_objTaskCb != null && m_objNotifyCb != null; }
+		}
+
+		/// <summary>
+		/// Property gets/sets the context object of the TaskRequest
+		/// </summary>
+		public object Context
+		{
+			get
+			{ return m_objCtx; }
+			set
+			{ m_objCtx = value; }
+		}
+
+		/// <summary>
+		/// Property gets/sets the TaskCallback of this TaskRequest
+		/// </summary>
+		public TaskCallback TaskCb
+		{
+			get
+			{ return m_objTaskCb; }
+			set
+			{
+				if( value == null )
+					throw new ArgumentNullException( "TaskCb", "Property cannot be set to null" );
+
+				m_objTaskCb = value;
+			}
+		}
+
+		/// <summary>
+		/// Property gets/sets the Notification callback of this TaskRequest
+		/// </summary>
+		public NotificationCallback NotifyCb
+		{
+			get
+			{ return m_objNotifyCb; }
+			set
+			{
+				if( value == null )
+					throw new ArgumentNullException( "NotifyCb", "Property cannot be set to null" );
+				m_objNotifyCb = value;
+			}
+		}
+
+		/// <summary>
+		/// Property gets/sets the TaskID of the TaskRequest
+		/// </summary>
+		public Guid TaskID
+		{
+			get
+			{ return m_taskID; }
+			set
+			{ m_taskID = value; }
+		}
+	}
+}
diff --git a/csharpsrc/AsyncCoreUnittest/App.ico b/csharpsrc/AsyncCoreUnittest/App.ico
new file mode 100644
index 0000000..3a5525f
Binary files /dev/null and b/csharpsrc/AsyncCoreUnittest/App.ico differ
diff --git a/csharpsrc/AsyncCoreUnittest/AssemblyInfo.cs b/csharpsrc/AsyncCoreUnittest/AssemblyInfo.cs
new file mode 100644
index 0000000..177a4f0
--- /dev/null
+++ b/csharpsrc/AsyncCoreUnittest/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\<configuration>. 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/csharpsrc/AsyncCoreUnittest/AsyncCoreUnittest.csproj b/csharpsrc/AsyncCoreUnittest/AsyncCoreUnittest.csproj
new file mode 100644
index 0000000..fcb40cf
--- /dev/null
+++ b/csharpsrc/AsyncCoreUnittest/AsyncCoreUnittest.csproj
@@ -0,0 +1,112 @@
+<VisualStudioProject>
+    <CSHARP
+        ProjectType = "Local"
+        ProductVersion = "7.0.9466"
+        SchemaVersion = "1.0"
+        ProjectGuid = "{B11691E6-B627-4DBF-869E-56CBDA119BBC}"
+    >
+        <Build>
+            <Settings
+                ApplicationIcon = "App.ico"
+                AssemblyKeyContainerName = ""
+                AssemblyName = "AsyncCoreUnittest"
+                AssemblyOriginatorKeyFile = ""
+                DefaultClientScript = "JScript"
+                DefaultHTMLPageLayout = "Grid"
+                DefaultTargetSchema = "IE50"
+                DelaySign = "false"
+                OutputType = "Exe"
+                RootNamespace = "AsyncCoreUnittest"
+                StartupObject = ""
+            >
+                <Config
+                    Name = "Debug"
+                    AllowUnsafeBlocks = "false"
+                    BaseAddress = "285212672"
+                    CheckForOverflowUnderflow = "false"
+                    ConfigurationOverrideFile = ""
+                    DefineConstants = "DEBUG;TRACE"
+                    DocumentationFile = ""
+                    DebugSymbols = "true"
+                    FileAlignment = "4096"
+                    IncrementalBuild = "true"
+                    Optimize = "false"
+                    OutputPath = "..\..\bin\"
+                    RegisterForComInterop = "false"
+                    RemoveIntegerChecks = "false"
+                    TreatWarningsAsErrors = "false"
+                    WarningLevel = "4"
+                />
+                <Config
+                    Name = "Release"
+                    AllowUnsafeBlocks = "false"
+                    BaseAddress = "285212672"
+                    CheckForOverflowUnderflow = "false"
+                    ConfigurationOverrideFile = ""
+                    DefineConstants = "TRACE"
+                    DocumentationFile = ""
+                    DebugSymbols = "false"
+                    FileAlignment = "4096"
+                    IncrementalBuild = "false"
+                    Optimize = "true"
+                    OutputPath = "bin\Release\"
+                    RegisterForComInterop = "false"
+                    RemoveIntegerChecks = "false"
+                    TreatWarningsAsErrors = "false"
+                    WarningLevel = "4"
+                />
+            </Settings>
+            <References>
+                <Reference
+                    Name = "System"
+                    AssemblyName = "System"
+                    HintPath = "..\..\..\..\..\WINDOWS\Microsoft.NET\Framework\v1.0.3705\System.dll"
+                />
+                <Reference
+                    Name = "System.Data"
+                    AssemblyName = "System.Data"
+                    HintPath = "..\..\..\..\..\WINDOWS\Microsoft.NET\Framework\v1.0.3705\System.Data.dll"
+                />
+                <Reference
+                    Name = "System.XML"
+                    AssemblyName = "System.XML"
+                    HintPath = "..\..\..\..\..\WINDOWS\Microsoft.NET\Framework\v1.0.3705\System.XML.dll"
+                />
+                <Reference
+                    Name = "AsyncCore"
+                    AssemblyName = "AsyncCore"
+                    HintPath = "..\..\bin\AsyncCore.dll"
+                />
+                <Reference
+                    Name = "System.Web.Services"
+                    AssemblyName = "System.Web.Services"
+                    HintPath = "..\..\..\..\..\WINDOWS\Microsoft.NET\Framework\v1.0.3705\System.Web.Services.dll"
+                />
+            </References>
+        </Build>
+        <Files>
+            <Include>
+                <File
+                    RelPath = "App.ico"
+                    BuildAction = "Content"
+                />
+                <File
+                    RelPath = "AssemblyInfo.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "Class1.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "WeatherRetriever.cs"
+                    SubType = "code"
+                    BuildAction = "Compile"
+                />
+            </Include>
+        </Files>
+    </CSHARP>
+</VisualStudioProject>
+
diff --git a/csharpsrc/AsyncCoreUnittest/AsyncCoreUnittest.csproj.user b/csharpsrc/AsyncCoreUnittest/AsyncCoreUnittest.csproj.user
new file mode 100644
index 0000000..831a9de
--- /dev/null
+++ b/csharpsrc/AsyncCoreUnittest/AsyncCoreUnittest.csproj.user
@@ -0,0 +1,48 @@
+<VisualStudioProject>
+    <CSHARP>
+        <Build>
+            <Settings ReferencePath = "C:\pslroot\psl\discus\bin\" >
+                <Config
+                    Name = "Debug"
+                    EnableASPDebugging = "false"
+                    EnableASPXDebugging = "false"
+                    EnableUnmanagedDebugging = "false"
+                    EnableSQLServerDebugging = "false"
+                    RemoteDebugEnabled = "false"
+                    RemoteDebugMachine = ""
+                    StartAction = "Project"
+                    StartArguments = ""
+                    StartPage = ""
+                    StartProgram = ""
+                    StartURL = ""
+                    StartWorkingDirectory = ""
+                    StartWithIE = "true"
+                />
+                <Config
+                    Name = "Release"
+                    EnableASPDebugging = "false"
+                    EnableASPXDebugging = "false"
+                    EnableUnmanagedDebugging = "false"
+                    EnableSQLServerDebugging = "false"
+                    RemoteDebugEnabled = "false"
+                    RemoteDebugMachine = ""
+                    StartAction = "Project"
+                    StartArguments = ""
+                    StartPage = ""
+                    StartProgram = ""
+                    StartURL = ""
+                    StartWorkingDirectory = ""
+                    StartWithIE = "true"
+                />
+            </Settings>
+        </Build>
+        <OtherProjectSettings
+            CopyProjectDestinationFolder = ""
+            CopyProjectUncPath = ""
+            CopyProjectOption = "0"
+            ProjectView = "ProjectFiles"
+            ProjectTrust = "0"
+        />
+    </CSHARP>
+</VisualStudioProject>
+
diff --git a/csharpsrc/AsyncCoreUnittest/Class1.cs b/csharpsrc/AsyncCoreUnittest/Class1.cs
new file mode 100644
index 0000000..b62235c
--- /dev/null
+++ b/csharpsrc/AsyncCoreUnittest/Class1.cs
@@ -0,0 +1,97 @@
+using System;
+using DynamicPxy;
+using PSL.AsyncCore;
+using System.Threading;
+
+namespace AsyncCoreUnittest
+{
+	/// <summary>
+	/// Summary description for Class1.
+	/// </summary>
+	class Class1
+	{
+		private readonly string m_strZip1 = "10025";
+		private readonly string m_strZip2 = "98055";
+
+		private object Task1( object objCtx )
+		{
+			// Do some web service invocations
+			WeatherRetriever wr = new WeatherRetriever();
+
+			//throw new Exception( "just becuz" );
+
+			object objRes = wr.GetTemperature( this.m_strZip1 );
+
+			return objRes;
+		}
+
+		private object Task2( object objCtx )
+		{
+			// Do some web service invocations
+
+			WeatherRetriever wr = new WeatherRetriever();
+			object objRes = wr.GetTemperature( this.m_strZip2 );
+
+			return objRes;
+		}
+
+		private object Task3( object objCtx )
+		{
+			WeatherRetriever wr = new WeatherRetriever();
+			object objRes = wr.GetWeather( this.m_strZip2 );
+
+			return objRes;
+		}
+
+		/// <summary>
+		/// The main entry point for the application.
+		/// </summary>
+		[STAThread]
+		static void Main(string[] args)
+		{
+			Class1 c = new Class1();
+			int nThreads = TaskPool.Threads;
+
+			AutoResetEvent condition = new AutoResetEvent( false );
+
+
+			TaskPool.QueueTask( new TaskRequest( new TaskRequest.TaskCallback( c.Task1 ), new TaskRequest.NotificationCallback( c.OnTaskDone ) ) );
+			TaskPool.QueueTask( new TaskRequest( new TaskRequest.TaskCallback( c.Task2 ), new TaskRequest.NotificationCallback( c.OnTaskDone ) ) );
+			TaskPool.QueueTask( new TaskRequest( new TaskRequest.TaskCallback( c.Task3 ), new TaskRequest.NotificationCallback( c.OnTaskDone ) ) );
+			// Create a task proxy
+			/*TaskProxy pxy = new TaskProxy( ref condition );
+
+			// Give the proxy tasks to wait on
+			Guid task1 = pxy.AddTaskRequest( new TaskRequest( new TaskRequest.TaskCallback( c.Task1 ), new TaskRequest.NotificationCallback( c.OnTaskDone ) ) );
+			Guid task2 = pxy.AddTaskRequest( new TaskRequest( new TaskRequest.TaskCallback( c.Task2 ), new TaskRequest.NotificationCallback( c.OnTaskDone ) ) );
+			Guid task3 = pxy.AddTaskRequest( new TaskRequest( new TaskRequest.TaskCallback( c.Task3 ), new TaskRequest.NotificationCallback( c.OnTaskDone ) ) );
+
+			// Let the proxy begin to wait on the queued requests
+			pxy.WaitOnTasks();
+
+			// Do misc operations in the meantime then...
+
+			// Wait until proxy signals results are ready
+			condition.WaitOne();
+
+			// Get results
+			TPTaskCompleteEventArgs e1 = pxy.QueryResult( task1 );
+			TPTaskCompleteEventArgs e2 = pxy.QueryResult( task2 );
+			TPTaskCompleteEventArgs e3 = pxy.QueryResult( task3 );
+			*/
+			TaskPool.Shutdown();
+		}
+
+		void OnTaskDone( TPTaskCompleteEventArgs tce )
+		{
+			object objRes = null;
+
+			if( !tce.HasErrors )
+				objRes = tce.Result;
+			else objRes = tce.ExceptionMessage;
+			objRes = tce.HasErrors ? tce.ExceptionMessage : tce.Result;
+
+			Console.WriteLine( objRes.ToString() );
+		}
+	}
+}
diff --git a/csharpsrc/AsyncCoreUnittest/WeatherRetriever.cs b/csharpsrc/AsyncCoreUnittest/WeatherRetriever.cs
new file mode 100644
index 0000000..b04cb4d
--- /dev/null
+++ b/csharpsrc/AsyncCoreUnittest/WeatherRetriever.cs
@@ -0,0 +1,87 @@
+namespace DynamicPxy {
+    using System.Diagnostics;
+    using System.Xml.Serialization;
+    using System;
+    using System.Web.Services.Protocols;
+    using System.ComponentModel;
+    using System.Web.Services;
+
+
+    /// <remarks/>
+    [System.Diagnostics.DebuggerStepThroughAttribute()]
+    [System.ComponentModel.DesignerCategoryAttribute("code")]
+    [System.Web.Services.WebServiceBindingAttribute(Name="WeatherRetrieverSoap", Namespace="http://tempuri.org/")]
+    public class WeatherRetriever : System.Web.Services.Protocols.SoapHttpClientProtocol {
+
+        /// <remarks/>
+        public WeatherRetriever() {
+            this.Url = "http://www.vbws.com/services/weatherretriever.asmx";
+        }
+
+        /// <remarks/>
+        [System.Web.Services.Protocols.SoapRpcMethodAttribute("http://tempuri.org/GetTemperature", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/")]
+        public System.Single GetTemperature(string zipCode) {
+            object[] results = this.Invoke("GetTemperature", new object[] {
+                        zipCode});
+            return ((System.Single)(results[0]));
+        }
+
+        /// <remarks/>
+        public System.IAsyncResult BeginGetTemperature(string zipCode, System.AsyncCallback callback, object asyncState) {
+            return this.BeginInvoke("GetTemperature", new object[] {
+                        zipCode}, callback, asyncState);
+        }
+
+        /// <remarks/>
+        public System.Single EndGetTemperature(System.IAsyncResult asyncResult) {
+            object[] results = this.EndInvoke(asyncResult);
+            return ((System.Single)(results[0]));
+        }
+
+        /// <remarks/>
+        [System.Web.Services.Protocols.SoapRpcMethodAttribute("http://tempuri.org/GetWeather", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/")]
+        public CurrentWeather GetWeather(string zipCode) {
+            object[] results = this.Invoke("GetWeather", new object[] {
+                        zipCode});
+            return ((CurrentWeather)(results[0]));
+        }
+
+        /// <remarks/>
+        public System.IAsyncResult BeginGetWeather(string zipCode, System.AsyncCallback callback, object asyncState) {
+            return this.BeginInvoke("GetWeather", new object[] {
+                        zipCode}, callback, asyncState);
+        }
+
+        /// <remarks/>
+        public CurrentWeather EndGetWeather(System.IAsyncResult asyncResult) {
+            object[] results = this.EndInvoke(asyncResult);
+            return ((CurrentWeather)(results[0]));
+        }
+    }
+
+    /// <remarks/>
+    [System.Xml.Serialization.SoapTypeAttribute("CurrentWeather", "http://tempuri.org/")]
+    public class CurrentWeather {
+
+        /// <remarks/>
+        public string LastUpdated;
+
+        /// <remarks/>
+        public string IconUrl;
+
+        /// <remarks/>
+        public string Conditions;
+
+        /// <remarks/>
+        public System.Single CurrentTemp;
+
+        /// <remarks/>
+        public System.Single Humidity;
+
+        /// <remarks/>
+        public System.Single Barometer;
+
+        /// <remarks/>
+        public string BarometerDirection;
+    }
+}