// Stephen Toub
// stoub@microsoft.com
//
// PriorityThreadPool.cs
// C# ThreadPool that executes waiting delegates in order of priority as supplied
// to QueueUserWorkItem.
//
// HISTORY:
// v1.0.0 - Original version
//
// October 4th, 2002
// v1.0.0
#region Namespaces
using System;
using System.Threading;
using System.Collections;
using Toub.Collections;
#endregion
namespace Toub.Threading
{
/// Managed thread pool.
public class PriorityThreadPool
{
#region Constants
/// Maximum number of threads the thread pool has at its disposal.
private const int _maxWorkerThreads = 20;
#endregion
#region Member Variables
/// Queue of all the callbacks waiting to be executed.
static PriorityQueue _waitingCallbacks;
///
/// Used to signal that a worker thread is needed for processing. Note that multiple
/// threads may be needed simultaneously and as such we use a semaphore instead of
/// an auto reset event.
///
static Semaphore _workerThreadNeeded;
/// List of all worker threads at the disposal of the thread pool.
static ArrayList _workerThreads;
/// Number of threads currently active.
static int _inUseThreads;
#endregion
#region Construction
/// Initialize the thread pool.
static PriorityThreadPool()
{
// Create our thread stores; we handle synchronization ourself
// as we may run into situtations where multiple operations need to be atomic.
// We keep track of the threads we've created just for good measure; not actually
// needed for any core functionality.
_waitingCallbacks = new PriorityQueue();
_workerThreads = new ArrayList();
_inUseThreads = 0;
// Create our "thread needed" event
_workerThreadNeeded = new Semaphore(0);
// Create all of the worker threads
for(int i=0; i<_maxWorkerThreads; i++)
{
// Create a new thread and add it to the list of threads.
Thread newThread = new Thread(new ThreadStart(ProcessQueuedItems));
_workerThreads.Add(newThread);
// Configure the new thread and start it
newThread.Name = "ManagedPoolThread #" + i.ToString();
newThread.IsBackground = true;
newThread.Start();
}
}
#endregion
#region Public Methods
/// Queues a user work item to the thread pool.
///
/// A WaitCallback representing the delegate to invoke when the thread in the
/// thread pool picks up the work item.
///
/// The priority for this callback.
public static void QueueUserWorkItem(WaitCallback callback, int priority)
{
// Queue the delegate with no state
QueueUserWorkItem(callback, null, priority);
}
/// Queues a user work item to the thread pool.
///
/// A WaitCallback representing the delegate to invoke when the thread in the
/// thread pool picks up the work item.
///
///
/// The object that is passed to the delegate when serviced from the thread pool.
///
/// The priority for this callback.
public static void QueueUserWorkItem(WaitCallback callback, object state, int priority)
{
// Create a waiting callback that contains the delegate and its state.
// At it to the processing queue, and signal that data is waiting.
WaitingCallback waiting = new WaitingCallback(callback, state);
lock(_waitingCallbacks.SyncRoot) { _waitingCallbacks.Enqueue(priority, waiting); }
_workerThreadNeeded.AddOne();
}
/// Empties the work queue of any queued work items.
public static void EmptyQueue()
{
lock(_waitingCallbacks.SyncRoot)
{
try
{
// Try to dispose of all remaining state
foreach(object obj in _waitingCallbacks)
{
((WaitingCallback)obj).Dispose();
}
}
catch
{
// Make sure an error isn't thrown.
}
// Clear all waiting items and reset the number of worker threads currently needed
// to be 0 (there is nothing for threads to do)
_waitingCallbacks.Clear();
_workerThreadNeeded.Reset(0);
}
}
#endregion
#region Properties
/// Gets the number of threads at the disposal of the thread pool.
public static int MaxThreads { get { return _maxWorkerThreads; } }
/// Gets the number of currently active threads in the thread pool.
public static int ActiveThreads { get { return _inUseThreads; } }
/// Gets the number of callback delegates currently waiting in the thread pool.
public static int WaitingCallbacks { get { lock(_waitingCallbacks.SyncRoot) { return _waitingCallbacks.Count; } } }
#endregion
#region Thread Processing
/// A thread worker function that processes items from the work queue.
private static void ProcessQueuedItems()
{
// Process indefinitely
while(true)
{
// Get the next item in the queue. If there is nothing there, go to sleep
// for a while until we're woken up when a callback is waiting.
WaitingCallback callback = null;
while (callback == null)
{
// Try to get the next callback available. We need to lock on the
// queue in order to make our count check and retrieval atomic.
lock(_waitingCallbacks.SyncRoot)
{
if (_waitingCallbacks.Count > 0)
{
callback = (WaitingCallback)_waitingCallbacks.Dequeue();
}
}
// If we can't get one, go to sleep.
if (callback == null) _workerThreadNeeded.WaitOne();
}
// We now have a callback. Execute it. Make sure to accurately
// record how many callbacks are currently executing.
try
{
Interlocked.Increment(ref _inUseThreads);
callback.Callback(callback.State);
}
catch
{
// Ignore any errors; not our problem.
}
finally
{
Interlocked.Decrement(ref _inUseThreads);
}
}
}
#endregion
/// Used to hold a callback delegate and the state for that delegate.
private class WaitingCallback : IDisposable
{
#region Member Variables
/// Callback delegate for the callback.
private WaitCallback _callback;
/// State with which to call the callback delegate.
private object _state;
#endregion
#region Construction
/// Initialize the callback holding object.
/// Callback delegate for the callback.
/// State with which to call the callback delegate.
public WaitingCallback(WaitCallback callback, object state)
{
_callback = callback;
_state = state;
}
// NOTE: Even though this implements IDisposable and it's good practice
// to also implement a finalizer when implementing Dispose, we're not going
// to, as there is no real need in this case.
#endregion
#region Properties
/// Gets the callback delegate for the callback.
public WaitCallback Callback { get { return _callback; } }
/// Gets the state with which to call the callback delegate.
public object State { get { return _state; } }
#endregion
#region Implementation of IDisposable
/// Disposes of the contained state if it is disposable.
public void Dispose()
{
if (State is IDisposable) ((IDisposable)State).Dispose();
}
#endregion
}
public static void QueueUserWorkItem(WaitCallback waitCallback)
{
throw new Exception("The method or operation is not implemented.");
}
}
}