using System; using System.Collections.Generic; using System.Threading; namespace JD.Threading { /// /// A threadpool. /// public sealed class ThreadPool : IDisposable { #region Inner Classes /// Used to hold a callback delegate and the state for that delegate. 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 /// /// Queue of all the callbacks waiting to be executed. /// private readonly Queue m_waitingCallbacks = new Queue(); /// /// 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. /// private readonly PVSemaphore m_workerThreadNeeded = new PVSemaphore(0); /// /// List of all worker threads at the disposal of the thread pool. /// private readonly List m_workerThreads = new List(); private readonly int m_maxThreads; /// /// Number of threads currently active. /// private int m_numThreadsInUse; /// /// Lockable object for the pool. /// private readonly object m_threadPoolLock = new object(); private bool m_disposed; #endregion #region Constructor /// /// Constructor. /// /// Number of worker threads to create. /// Thrown if is zero or negative. public ThreadPool(int numWorkerThreads) { if (numWorkerThreads < 1) { throw new ArgumentOutOfRangeException("numWorkerThreads", numWorkerThreads, "Threadpools need at least one thread."); } m_maxThreads = numWorkerThreads; Initialize(); } /// /// Finalizer. /// ~ThreadPool() { ((IDisposable)this).Dispose(); } /// /// IDisposble. /// void IDisposable.Dispose() { if (!m_disposed) { Close(); m_disposed = true; } GC.SuppressFinalize(this); } #endregion #region Properties /// /// Total number of threads in this threadpool. /// public int MaxThreads { get { return m_maxThreads; } } /// /// Gets the number of currently active threads in the thread pool. /// public int ActiveThreads { get { return m_numThreadsInUse; } } /// /// Gets the number of callback delegates currently waiting in the thread pool. /// public int WaitingCallbacks { get { lock (m_threadPoolLock) { return m_waitingCallbacks.Count; } } } #endregion #region 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. /// /// This threadpool has been disposed. public void QueueUserWorkItem(WaitCallback callback) { // Queue the delegate with no state QueueUserWorkItem(callback, null); } /// /// 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. /// /// This threadpool has been disposed. 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(); } /// /// Empties the work queue of any queued work items. Resets all threads in the pool. /// /// This threadpool has been disposed. public void Reset() { if (m_disposed) { throw new ObjectDisposedException("ThreadPool"); } lock (m_threadPoolLock) { Close(); // Reinitialize the pool (create new threads, etc.) Initialize(); } } /// /// Cancels all the works currently queued. /// /// Whether to abort the threads that are currently executing or to wait for them to complete. 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; } /// /// A thread worker function that processes items from the work queue. /// 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 /// Event raised when there is an exception on a threadpool thread. public event UnhandledExceptionEventHandler UnhandledException; private void OnUnhandledException(UnhandledExceptionEventArgs e) { UnhandledExceptionEventHandler handler = UnhandledException; if (handler != null) { handler(this, e); } } #endregion } }