Thread Manager
In the framework, the ThreadManager static class provides support for starting work items. The Start() method takes a WorkItem as a parameter and returns a WorkItemThread representing the thread running the work item. A WorkItem instance can only be associated with one thread at a time. Furthermore, a WorkItemThread can only be used once.
There are additional methods in ThreadManager to cancel or terminate work item threads. These methods can be used to clean up running work items prior to the application closing. Additional methods can be added as needed. Listing Three provides a simplified view of the class.
namespace DDJ.Threading { // Actual implementation of ThreadManager static class public class ThreadManagerBase { //Public Members #region Methods // Cancels all running work items. // This method does not wait for the work items to cancel. // Work items that can not be cancelled are not affected. public void CancelAll ( ) { StopBase(false); } // Starts a work item on a separate thread. // The work item will be scheduled for execution. public WorkItemThread Start ( WorkItem work ) { //Validate if (work == null) throw new ArgumentNullException("work"); if (work.InnerThread != null) throw new ArgumentException( "Work item already associated with another thread.", "work"); //Schedule it for execution WorkItemThread thread = new WorkItemThread(work); work.SetThread(thread); ExecuteWorkItem(thread); return thread; } // Terminates all running work items. // This method does not wait for the work items to terminate. // All work items are terminated even if they don't support // cancellation. public void TerminateAll ( ) { StopBase(true); } // Private Members #region Methods private void ExecuteWorkItem ( WorkItemThread thread ) { //Create a real thread to back it Thread realThread = new Thread( new ThreadStart(thread.DoWork)); thread.StateChanged += OnThreadStateChanged; lock (m_Threads) { m_Threads.Add(thread); }; //Go... realThread.Start(); } private void OnThreadStateChanged ( object sender, EventArgs e ) { //If the thread is finished switch (((WorkItemThread)sender).State) { case WorkItemThreadState.Cancelled: case WorkItemThreadState.Finished: case WorkItemThreadState.Terminated: { //Lock the list lock (m_Threads) { //Remove from the list m_Threads.Remove((WorkItemThread)sender); }; break; }; }; } private void StopBase ( bool force ) { Collection<WorkItemThread> threads = new Collection<WorkItemThread>(); //Lock the list lock (m_Threads) { //Enumerate the list for (int nIdx = 0; nIdx < m_Threads.Count; ++nIdx) { if (force || m_Threads[nIdx].CanCancel) { threads.Add(m_Threads[nIdx]); m_Threads.RemoveAt(nIdx); --nIdx; }; }; }; //Stop each one foreach (WorkItemThread thread in threads) { if (force) thread.Terminate(); else thread.Cancel(); }; } private Collection<WorkItemThread> m_Threads = new Collection<WorkItemThread>(); } }
Each WorkItemThread gets its own .NET thread to run on. This is good when dealing with a small number of work items, but as more work items are added, the performance will decline. A better solution would be to create a threading pool. Similar to the ThreadPool in .NET, the manager would allocate (either initially or on-demand) a fixed number of threads. Whenever a new WorkItemThread is created, it is assigned to one of the existing threads. When the work item thread is finished, the associated thread is returned to the thread pool. If a new WorkItemThread is created but there are no threads available, then the work item thread is implicitly paused until a thread becomes available. Control can still return to the caller (perhaps with some sort of indicator). This enhancement is left to the reader to implement. One word of caution about this enhancement: Pausing all work items could effectively prevent new work items from running. If a thread pool is used, then some thought should be given to releasing the underlying thread whenever a work item thread is paused such that waiting work items can be run.