397 lines
12 KiB
C#
397 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
|
|
namespace JD.Threading
|
|
{
|
|
/// <summary>
|
|
/// A threadpool.
|
|
/// </summary>
|
|
public sealed class ThreadPool : IDisposable
|
|
{
|
|
#region Inner Classes
|
|
|
|
/// <summary>Used to hold a callback delegate and the state for that delegate.</summary>
|
|
private class WaitingCallback
|
|
{
|
|
#region Variables
|
|
|
|
private readonly WaitCallback m_callback;
|
|
private readonly object m_state;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
internal WaitingCallback(WaitCallback callback, object state)
|
|
{
|
|
System.Diagnostics.Debug.Assert(callback != null);
|
|
|
|
m_callback = callback;
|
|
m_state = state;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public WaitCallback Callback
|
|
{
|
|
get
|
|
{
|
|
return m_callback;
|
|
}
|
|
}
|
|
|
|
public object State
|
|
{
|
|
get
|
|
{
|
|
return m_state;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Variables
|
|
|
|
/// <summary>
|
|
/// Queue of all the callbacks waiting to be executed.
|
|
/// </summary>
|
|
private readonly Queue<WaitingCallback> m_waitingCallbacks = new Queue<WaitingCallback>();
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private readonly PVSemaphore m_workerThreadNeeded = new PVSemaphore(0);
|
|
|
|
/// <summary>
|
|
/// List of all worker threads at the disposal of the thread pool.
|
|
/// </summary>
|
|
private readonly List<Thread> m_workerThreads = new List<Thread>();
|
|
|
|
private readonly int m_maxThreads;
|
|
|
|
/// <summary>
|
|
/// Number of threads currently active.
|
|
/// </summary>
|
|
private int m_numThreadsInUse;
|
|
|
|
/// <summary>
|
|
/// Lockable object for the pool.
|
|
/// </summary>
|
|
private readonly object m_threadPoolLock = new object();
|
|
|
|
private bool m_disposed;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="numWorkerThreads">Number of worker threads to create.</param>
|
|
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="numWorkerThreads"/> is zero or negative.</exception>
|
|
public ThreadPool(int numWorkerThreads)
|
|
{
|
|
if (numWorkerThreads < 1)
|
|
{
|
|
throw new ArgumentOutOfRangeException("numWorkerThreads", numWorkerThreads, "Threadpools need at least one thread.");
|
|
}
|
|
|
|
m_maxThreads = numWorkerThreads;
|
|
Initialize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finalizer.
|
|
/// </summary>
|
|
~ThreadPool()
|
|
{
|
|
((IDisposable)this).Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// IDisposble.
|
|
/// </summary>
|
|
void IDisposable.Dispose()
|
|
{
|
|
if (!m_disposed)
|
|
{
|
|
Close();
|
|
m_disposed = true;
|
|
}
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Total number of threads in this threadpool.
|
|
/// </summary>
|
|
public int MaxThreads
|
|
{
|
|
get
|
|
{
|
|
return m_maxThreads;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the number of currently active threads in the thread pool.
|
|
/// </summary>
|
|
public int ActiveThreads
|
|
{
|
|
get
|
|
{
|
|
return m_numThreadsInUse;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the number of callback delegates currently waiting in the thread pool.
|
|
/// </summary>
|
|
public int WaitingCallbacks
|
|
{
|
|
get
|
|
{
|
|
lock (m_threadPoolLock)
|
|
{
|
|
return m_waitingCallbacks.Count;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Queues a user work item to the thread pool.
|
|
/// </summary>
|
|
/// <param name="callback">
|
|
/// A WaitCallback representing the delegate to invoke when the thread in the
|
|
/// thread pool picks up the work item.
|
|
/// </param>
|
|
/// <exception cref="ObjectDisposedException">This threadpool has been disposed.</exception>
|
|
public void QueueUserWorkItem(WaitCallback callback)
|
|
{
|
|
// Queue the delegate with no state
|
|
QueueUserWorkItem(callback, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Queues a user work item to the thread pool.
|
|
/// </summary>
|
|
/// <param name="callback">
|
|
/// A WaitCallback representing the delegate to invoke when the thread in the
|
|
/// thread pool picks up the work item.
|
|
/// </param>
|
|
/// <param name="state">
|
|
/// The object that is passed to the delegate when serviced from the thread pool.
|
|
/// </param>
|
|
/// <exception cref="ObjectDisposedException">This threadpool has been disposed.</exception>
|
|
public void QueueUserWorkItem(WaitCallback callback, object state)
|
|
{
|
|
if (m_disposed)
|
|
{
|
|
throw new ObjectDisposedException("ThreadPool");
|
|
}
|
|
|
|
// 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 (m_threadPoolLock)
|
|
{
|
|
m_waitingCallbacks.Enqueue(waiting);
|
|
}
|
|
|
|
m_workerThreadNeeded.AddOne();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Empties the work queue of any queued work items. Resets all threads in the pool.
|
|
/// </summary>
|
|
/// <exception cref="ObjectDisposedException">This threadpool has been disposed.</exception>
|
|
public void Reset()
|
|
{
|
|
if (m_disposed)
|
|
{
|
|
throw new ObjectDisposedException("ThreadPool");
|
|
}
|
|
|
|
lock (m_threadPoolLock)
|
|
{
|
|
Close();
|
|
|
|
// Reinitialize the pool (create new threads, etc.)
|
|
Initialize();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancels all the works currently queued.
|
|
/// </summary>
|
|
/// <param name="abortCurrentlyRunningThreads">Whether to abort the threads that are currently executing or to wait for them to complete.</param>
|
|
public void Cancel(bool abortCurrentlyRunningThreads)
|
|
{
|
|
lock (m_threadPoolLock)
|
|
{
|
|
ClearWaitingQueue();
|
|
|
|
if (abortCurrentlyRunningThreads)
|
|
{
|
|
AbortAllThreads();
|
|
}
|
|
}
|
|
|
|
// Semi-hack
|
|
while (ActiveThreads > 0)
|
|
{
|
|
Thread.Sleep(50);
|
|
}
|
|
}
|
|
|
|
private void Initialize()
|
|
{
|
|
System.Diagnostics.Debug.Assert(m_waitingCallbacks.Count == 0);
|
|
System.Diagnostics.Debug.Assert(m_workerThreads.Count == 0);
|
|
System.Diagnostics.Debug.Assert(m_numThreadsInUse == 0);
|
|
|
|
// Create all of the worker threads
|
|
for (int i = 0; i < m_maxThreads; i++)
|
|
{
|
|
// Create a new thread and add it to the list of threads.
|
|
Thread newThread = new Thread(new ThreadStart(ProcessQueuedItems));
|
|
m_workerThreads.Add(newThread);
|
|
|
|
// Configure the new thread and start it
|
|
newThread.Name = "Threadpool worker thread #" + i.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
|
newThread.IsBackground = true;
|
|
newThread.Start();
|
|
}
|
|
|
|
System.Diagnostics.Debug.Assert(m_workerThreads.Count == m_maxThreads);
|
|
}
|
|
|
|
private void Close()
|
|
{
|
|
ClearWaitingQueue();
|
|
AbortAllThreads();
|
|
}
|
|
|
|
private void ClearWaitingQueue()
|
|
{
|
|
// Cleanup any waiting callbacks
|
|
try
|
|
{
|
|
// Try to dispose of all remaining state
|
|
foreach (WaitingCallback waitingCallback in m_waitingCallbacks)
|
|
{
|
|
IDisposable disposable = waitingCallback.State as IDisposable;
|
|
if (disposable != null)
|
|
{
|
|
disposable.Dispose();
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
m_waitingCallbacks.Clear();
|
|
|
|
// Reset our "thread needed" event
|
|
m_workerThreadNeeded.Reset(0);
|
|
}
|
|
|
|
private void AbortAllThreads()
|
|
{
|
|
// Shutdown all existing threads
|
|
try
|
|
{
|
|
foreach (Thread thread in m_workerThreads)
|
|
{
|
|
thread.Abort("Closing threadpool.");
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
m_workerThreads.Clear();
|
|
m_numThreadsInUse = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A thread worker function that processes items from the work queue.
|
|
/// </summary>
|
|
private void ProcessQueuedItems()
|
|
{
|
|
// Process indefinitely
|
|
while (true)
|
|
{
|
|
m_workerThreadNeeded.WaitOne();
|
|
|
|
// 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;
|
|
|
|
// 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 (m_threadPoolLock)
|
|
{
|
|
if (m_waitingCallbacks.Count > 0)
|
|
{
|
|
callback = m_waitingCallbacks.Dequeue();
|
|
}
|
|
}
|
|
|
|
if (callback != null)
|
|
{
|
|
// We now have a callback. Execute it. Make sure to accurately
|
|
// record how many callbacks are currently executing.
|
|
try
|
|
{
|
|
Interlocked.Increment(ref m_numThreadsInUse);
|
|
callback.Callback(callback.State);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
try
|
|
{
|
|
OnUnhandledException(new UnhandledExceptionEventArgs(e, false));
|
|
}
|
|
catch { }
|
|
}
|
|
finally
|
|
{
|
|
Interlocked.Decrement(ref m_numThreadsInUse);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
/// <summary>Event raised when there is an exception on a threadpool thread.</summary>
|
|
public event UnhandledExceptionEventHandler UnhandledException;
|
|
|
|
private void OnUnhandledException(UnhandledExceptionEventArgs e)
|
|
{
|
|
UnhandledExceptionEventHandler handler = UnhandledException;
|
|
if (handler != null)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|