/* * 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.Threading; using ProcessHacker.Common.Threading; namespace ProcessHacker.Common { /// /// Manages a work queue which is executed by worker threads. /// public sealed class WorkQueue { /// /// Represents a work item to be executed on a worker thread. /// public sealed class WorkItem { private WorkQueue _owner; private string _tag; private Delegate _work; private object[] _args; private bool _enabled = true; private FastEvent _completedEvent = new FastEvent(false); private object _result; private Exception _exception; internal WorkItem(WorkQueue owner, Delegate work, object[] args) : this(owner, work, args, null) { } internal WorkItem(WorkQueue owner, Delegate work, object[] args, string tag) { _owner = owner; _work = work; _args = args; _tag = tag; } public Delegate Work { get { return _work; } } public object[] Arguments { get { return _args; } } /// /// The tag associated with the work item. /// public string Tag { get { return _tag; } } /// /// Whether the work item is to be executed. /// internal bool Enabled { get { return _enabled; } set { _enabled = value; } } /// /// Whether the work item has been completed. /// public bool Completed { get { return _completedEvent.Value; } } /// /// The value returned by the target method. /// public object Result { get { return _result; } } /// /// The exception thrown by the work item target, if any. /// public Exception Exception { get { return _exception; } } /// /// If the work item has not been executed yet, prevents the /// work item from executing. Otherwise, takes no action. /// /// True if the work item has not been executed yet; otherwise false. public bool Abort() { return _owner.RemoveQueuedWorkItem(this); } /// /// Waits for the work item to complete and returns the result. /// /// The value returned by the target method. public object GetResult() { this.WaitOne(); return _result; } /// /// Performs the work. /// internal void PerformWork() { if (!_enabled) return; try { if (_args == null) _result = _work.Method.Invoke(_work.Target, null); else _result = _work.Method.Invoke(_work.Target, _args.Length != 0 ? _args : null); } catch (Exception ex) { _exception = ex; } _completedEvent.Set(); } /// /// Waits for the work item to be completed. /// /// Always returns true. public bool WaitOne() { return this.WaitOne(-1); } /// /// Waits for the work item to be completed. /// /// The timeout for the wait operation. /// /// True if the work item was completed within the timeout /// (or was already completed); otherwise false. /// public bool WaitOne(int timeout) { return _completedEvent.Wait(timeout); } } 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 WorkItem GlobalQueueWorkItem(Delegate work) { return _globalWorkQueue.QueueWorkItem(work); } /// /// Queues work for the global work queue. /// /// The work to be executed. /// The arguments to pass to the delegate. public static WorkItem GlobalQueueWorkItem(Delegate work, params object[] args) { return _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 WorkItem GlobalQueueWorkItemTag(Delegate work, string tag) { return _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 WorkItem GlobalQueueWorkItemTag(Delegate work, string tag, params object[] args) { return _globalWorkQueue.QueueWorkItemTag(work, tag, true, args); } /// /// The work queue. This object is used as a lock. /// 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 minimum number of worker threads. Worker threads will be created /// as necessary and the number of worker threads will never drop below /// this number. /// private int _minWorkerThreads = 0; /// /// The pool of worker threads. This object is used as a lock. /// 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; /// /// If true, prevents new work items from being queued. /// private volatile bool _isJoining = 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 maximum number of worker threads. /// public int MaxWorkerThreads { get { return _maxWorkerThreads; } set { _maxWorkerThreads = value; } } /// /// Gets or sets the minimum number of worker threads. /// public int MinWorkerThreads { get { return _minWorkerThreads; } set { _minWorkerThreads = value; } } /// /// Gets or sets the time, in milliseconds, after which a /// worker thread with no work will terminate. Specify 0 so that /// worker threads will terminate immediately, or specify -1 so that /// worker threads will wait indefinitely for work. /// 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 worker threads if necessary to satisfy the /// worker thread minimum. /// public void CreateMinimumWorkerThreads() { if (_workerThreads.Count < _minWorkerThreads) { lock (_workerThreads) { // Create worker threads until we have enough. while (_workerThreads.Count < _minWorkerThreads) this.CreateWorkerThread(); } } } /// /// Creates a worker thread. /// private void CreateWorkerThread() { Thread workThread = new Thread(this.WorkerThreadStart, Utils.SixteenthStackSize); 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(); } /// /// Waits for all work items to complete and prevents new work items from being queued. /// public void JoinAll() { _isJoining = true; // Check for work items. while (_workQueue.Count > 0) { WorkItem workItem = null; // Lock and re-check. lock (_workQueue) { if (_workQueue.Count > 0) workItem = _workQueue.Peek(); else continue; } // Wait for this work item to finish. workItem.WaitOne(); } } /// /// Removes the work item from the work queue. /// /// The work item to remove /// If the work item was in the work queue, true. Otherwise, false. public bool RemoveQueuedWorkItem(WorkItem workItem) { // Lock the work queue to prevent data corruption. lock (_workQueue) { // Check if the work queue (still) contains the work item. if (_workQueue.Contains(workItem)) { // The work item is in the queue. Prevent it from executing. workItem.Enabled = false; return true; } else { // The work item is no longer in the queue. return false; } } } /// /// Allows new work items to be queued. /// public void ResetJoin() { _isJoining = false; } /// /// Queues work for the worker thread(s). /// /// The work to be performed. public WorkItem QueueWorkItem(Delegate work) { return 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 WorkItem QueueWorkItem(Delegate work, params object[] args) { return 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 WorkItem QueueWorkItemTag(Delegate work, string tag) { return 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 WorkItem QueueWorkItemTag(Delegate work, string tag, params object[] args) { return 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 WorkItem QueueWorkItemTag(Delegate work, string tag, bool isArray, object[] args) { WorkItem workItem; // Can't queue any work items if joining. if (_isJoining) return null; lock (_workQueue) { _workQueue.Enqueue(workItem = new WorkItem(this, work, args, tag)); Monitor.Pulse(_workQueue); } // 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(); } } } } return workItem; } /// /// 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) { // Check the minimum as well. if (_workerThreads.Count > _maxWorkerThreads && _workerThreads.Count > _minWorkerThreads) { // We have an excess amount of worker threads. this.DestroyWorkerThread(); return; } } } // Check for work. if (_workQueue.Count > 0) { WorkItem workItem = null; // There is work, but we must lock and re-check. lock (_workQueue) { if (_workQueue.Count > 0) workItem = _workQueue.Dequeue(); else continue; } Interlocked.Increment(ref _busyCount); workItem.PerformWork(); Interlocked.Decrement(ref _busyCount); } else { // No work available. Wait for work. bool workArrived = false; lock (_workQueue) workArrived = Monitor.Wait(_workQueue, _noWorkTimeout); if (workArrived) { // Work arrived. Go back so we can perform it. continue; } else { // No work arrived during the timeout period. Delete the thread. lock (_workerThreads) { // Check the minimum. if (_workerThreads.Count > _minWorkerThreads) { this.DestroyWorkerThread(); return; } } } } } } } }