/*
* 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;
}
}
}
}
}
}