/* * Process Hacker - * thread pool/work queue * * Copyright (C) 2009 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace ProcessHacker { public delegate void Action(); public delegate void Action(T a1); public delegate void Action(T a1, U a2); public delegate void Action(T a1, U a2, V a3); public delegate void Action(T a1, U a2, V a3, W a4); public delegate void Action(T a1, U a2, V a3, W a4, X a5); public delegate void Action(T a1, U a2, V a3, W a4, X a5, Y a6); public delegate void Action(T a1, U a2, V a3, W a4, X a5, Y a6, Z a7); /// /// Manages a work queue which is executed by worker threads. /// public class WorkQueue { public class WorkItem { private string _tag; private Delegate _work; private object[] _args; public WorkItem(Delegate work, object[] args) : this(work, args, null) { } public WorkItem(Delegate work, object[] args, string tag) { _work = work; _args = args; _tag = tag; } public Delegate Work { get { return _work; } } public object[] Arguments { get { return _args; } } public string Tag { get { return _tag; } } public void PerformWork() { if (_args == null) _work.Method.Invoke(_work.Target, null); else _work.Method.Invoke(_work.Target, _args.Length != 0 ? _args : null); } } private static WorkQueue _globalWorkQueue = new WorkQueue(); /// /// Gets the global work queue instance. /// public static WorkQueue GlobalWorkQueue { get { return _globalWorkQueue; } } /// /// Queues work for the global work queue. /// /// The work to be executed. public static void GlobalQueueWorkItem(Delegate work) { _globalWorkQueue.QueueWorkItem(work); } /// /// Queues work for the global work queue. /// /// The work to be executed. /// The arguments to pass to the delegate. public static void GlobalQueueWorkItem(Delegate work, params object[] args) { _globalWorkQueue.QueueWorkItemTag(work, null, true, args); } /// /// Queues work for the global work queue. /// /// The work to be executed. /// A tag for the work item. public static void GlobalQueueWorkItemTag(Delegate work, string tag) { _globalWorkQueue.QueueWorkItemTag(work, tag, true, null); } /// /// Queues work for the global work queue. /// /// The work to be executed. /// A tag for the work item. /// The arguments to pass to the delegate. public static void GlobalQueueWorkItemTag(Delegate work, string tag, params object[] args) { _globalWorkQueue.QueueWorkItemTag(work, tag, true, args); } /// /// The work queue. /// private Queue _workQueue = new Queue(); /// /// The maximum number of worker threads. If there are less worker threads /// than this limit, they will be created as necessary. If there are more /// worker threads than this limit, they will terminate once they have /// finished processing their current work items. /// private int _maxWorkerThreads = 1; /// /// The pool of worker threads. /// private Dictionary _workerThreads = new Dictionary(); /// /// The number of worker threads which are currently running work. /// private int _busyCount = 0; /// /// A worker will block on the work-arrived event for this amount of time /// before terminating. /// private int _noWorkTimeout = 1000; /// /// Signalled whenever work arrives. If worker threads have no immediate work, /// they will block on this event. /// private AutoResetEvent _workArrivedEvent = new AutoResetEvent(false); /// /// Creates a new work queue. /// public WorkQueue() { } /// /// Gets the number of worker threads that are currently busy. /// public int BusyCount { get { return _busyCount; } } /// /// Gets or sets the worker thread limit. /// public int MaxWorkerThreads { get { return _maxWorkerThreads; } set { _maxWorkerThreads = value; } } /// /// Gets or sets the time, in milliseconds, after which a /// worker thread with no work will terminate. /// public int NoWorkTimeout { get { return _noWorkTimeout; } set { _noWorkTimeout = value; } } /// /// Gets the number of queued work items. /// public int QueuedCount { get { return _workQueue.Count; } } /// /// Gets the number of worker threads that are alive. /// public int WorkerCount { get { return _workerThreads.Count; } } /// /// Creates a worker thread. /// private void CreateWorkerThread() { Thread workThread = new Thread(this.WorkerThreadStart); workThread.IsBackground = true; workThread.Priority = ThreadPriority.Lowest; workThread.SetApartmentState(ApartmentState.STA); _workerThreads.Add(workThread.ManagedThreadId, workThread); workThread.Start(); } /// /// Destroys the current worker thread. /// private void DestroyWorkerThread() { _workerThreads.Remove(Thread.CurrentThread.ManagedThreadId); } /// /// Gets the work items in the queue. /// /// An array of WorkItem objects. public WorkItem[] GetQueuedWorkItems() { lock (_workQueue) return _workQueue.ToArray(); } /// /// Queues work for the worker thread(s). /// /// The work to be performed. public void QueueWorkItem(Delegate work) { this.QueueWorkItemTag(work, null, true, null); } /// /// Queues work for the worker thread(s). /// /// The work to be performed. /// The arguments to pass to the delegate. public void QueueWorkItem(Delegate work, params object[] args) { this.QueueWorkItemTag(work, null, true, args); } /// /// Queues work for the worker thread(s). /// /// The work to be performed. /// A tag for the work item. public void QueueWorkItemTag(Delegate work, string tag) { this.QueueWorkItemTag(work, tag, true, null); } /// /// Queues work for the worker thread(s). /// /// The work to be performed. /// A tag for the work item. /// The arguments to pass to the delegate. public void QueueWorkItemTag(Delegate work, string tag, params object[] args) { this.QueueWorkItemTag(work, tag, true, args); } /// /// Queues work for the worker thread(s). /// /// The work to be performed. /// A tag for the work item. /// Ignored. /// The arguments to pass to the delegate. public void QueueWorkItemTag(Delegate work, string tag, bool isArray, object[] args) { lock (_workQueue) _workQueue.Enqueue(new WorkItem(work, args, tag)); _workArrivedEvent.Set(); // Check if all worker threads are currently busy. if (Thread.VolatileRead(ref _busyCount) == _workerThreads.Count) { // Check if we still have available worker threads if (_workerThreads.Count < _maxWorkerThreads) { // We do, so we must lock and re-check. lock (_workerThreads) { if (_workerThreads.Count < _maxWorkerThreads) { this.CreateWorkerThread(); } } } } } /// /// The entry point for all worker threads. /// private void WorkerThreadStart() { while (true) { // Check if we have more worker threads than the limit. if (_workerThreads.Count > _maxWorkerThreads) { // Lock and re-check. lock (_workerThreads) { if (_workerThreads.Count > _maxWorkerThreads) { // We have an excess amount of worker threads. this.DestroyWorkerThread(); return; } } } // Check for work if (_workQueue.Count > 0) { WorkItem item = null; // There is work, but we must lock and re-check. lock (_workQueue) { if (_workQueue.Count > 0) item = _workQueue.Dequeue(); else continue; } Interlocked.Increment(ref _busyCount); try { item.PerformWork(); } catch (Exception ex) { Logging.Log(ex); } Interlocked.Decrement(ref _busyCount); } else { // No work available. Wait for work. if (_workArrivedEvent.WaitOne(_noWorkTimeout, false)) { // Work arrived. Go back so we can perform it. continue; } else { // No work arrived during the timeout period. Delete the thread. lock (_workerThreads) this.DestroyWorkerThread(); return; } } } } } }