JD2022-TU1/main/tools/framework/JD.Threading/ThreadPool.cs

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
}
}