//****************************************************************************************************** // ProcessList.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 01/07/2006 - J. Ritchie Carroll // Generated original version of source code. // 02/12/2006 - J. Ritchie Carroll // Added multi-item bulk processing functionality. // 03/21/2007 - J. Ritchie Carroll // Added "ItemsBeingProcessed" property to return current total number of items being processed. // Added "Flush" method to allow any remaining items in list to be processed before shutdown. // 04/05/2007 - J. Ritchie Carroll // Added "RequeueMode" properties to allow users to specify how data gets reinserted back into // the list (prefix or suffix) after processing timeouts or exceptions. // 07/12/2007 - Pinal C. Patel // Modified the code for "Flush" method to correctly implement IDisposable interface. // 08/01/2007 - J. Ritchie Carroll // Added some minor optimizations where practical. // 08/17/2007 - J. Ritchie Carroll // Removed IDisposable implementation because of continued flushing errors. // 08/17/2007 - Darrell Zuercher // Edited code comments. // 11/05/2007 - J. Ritchie Carroll // Modified flush to complete tasks on calling thread - this avoids errors when timer // gets disposed before flush call. // 02/20/2008 - J. Ritchie Carroll // Implemented standard IDisposable pattern. // 09/11/2008 - J. Ritchie Carroll // Converted to C#. // 11/06/2008 - J. Ritchie Carroll // Added CurrentStatistics property to return run-time statistics as a group. // 02/23/2009 - Josh L. Patterson // Edited Code Comments. // 08/05/2009 - Josh L. Patterson // Edited Code Comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 01/04/2010 - J. Ritchie Carroll // Removed hard thread abort from shutdown which will allow current processing items // to complete before terminating thread. // 06/21/2010 - Stephen C. Wills // Modified Dispose to fix potential concurrency issues. // 11/23/2011 - J. Ritchie Carroll // Modified to support buffer optimized ISupportBinaryImage. // 01/24/2012 - Pinal C. Patel // Modified ProcessTimerThreadProc() method to perform null reference check to avoid an exception // when the object is being disposed. // 11/27/2012 - J. Ritchie Carroll // Modified process queue to be based on ConcurrentQueue. New ProcessList class now assumes // the List based functionality of original code. // 12/13/2012 - Starlynn Danyelle Gilliam // Modified Header. // 04/03/2014 - J. Ritchie Carroll // Changed to List with sync-lock operation since this is faster than ConcurrentQueue // when dealing with collections of items. ProcessQueue was dropped in lieu of ProcessList. // //****************************************************************************************************** using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Timers; using GSF.Threading; using GSF.Units; using Timer = System.Timers.Timer; namespace GSF.Collections { #region [ Enumerations ] /// /// Enumeration of possible threading modes. /// public enum QueueThreadingMode { /// /// Processes several items in the at once on different threads, where processing order is not important. /// Asynchronous, /// /// Processes items in the one at a time on a single thread, where processing order is important. /// Synchronous } /// /// Enumeration of possible processing styles. /// public enum QueueProcessingStyle { /// /// Defines processing delegate to process only one item at a time. /// /// /// This is the typical when the is asynchronous. /// OneAtATime, /// /// Defines processing delegate to process all currently available items in the . /// Items are passed into delegate as an array. /// /// /// This is the optimal when the is synchronous. /// ManyAtOnce } /// /// Enumeration of possible requeue reasons. /// public enum RequeueReason { /// /// Requeuing item since it cannot be processed at this time. /// CannotProcess, /// /// Requeuing item due to an exception. /// Exception, /// /// Requeing item due to timeout. /// Timeout } /// /// Enumeration of possible requeue modes. /// public enum RequeueMode { /// /// Requeues item at the beginning of the . /// Prefix, /// /// Requeues item at the end of the . /// Suffix } #endregion /// /// Represents a thread-safe (via locking) list of items, based on , that get processed on independent threads with a consumer provided function. /// /// Type of object to process /// /// This class acts as a strongly-typed collection of objects to be processed. /// Note that the will not start processing until the Start method is called. /// public class ProcessQueue : IList, IProvideStatus, ISupportLifecycle { #region [ Members ] // Nested Types // Limits item processing time, if requested. private sealed class TemporalTask : IDisposable { private readonly ProcessQueue m_parent; private readonly CancellationTokenSource m_tokenSource; private readonly Task m_task; private readonly T m_item; private readonly T[] m_items; private bool m_disposed; private TemporalTask(ProcessQueue parent, T item) { m_parent = parent; m_item = item; Thread.MemoryBarrier(); m_tokenSource = new CancellationTokenSource(); m_task = Task.Factory.StartNew(ProcessItem, m_tokenSource.Token); } private TemporalTask(ProcessQueue parent, T[] items) { m_parent = parent; m_items = items; Thread.MemoryBarrier(); m_tokenSource = new CancellationTokenSource(); m_task = Task.Factory.StartNew(ProcessItems, m_tokenSource.Token); } ~TemporalTask() { Dispose(false); } private void ProcessItem() { // In rare cases, this method was throwing a NullReferenceException which seems // to indicate that m_parent is null. At first glance, this should not be possible // as m_parent is readonly and the constructor is invoked by m_parent itself. // However, note that m_task is also assigned in the constructor by calling // Task.Factory.StartNew, which executes this method on a separate thread. If we // instead assume that the assignment operations in the constructor could be // reordered, or if this method executes on a separate processor before the first // processor's cache is flushed, then it should be possible for m_parent and even // m_item to be null. The memory barriers should prevent this reordering of events. Thread.MemoryBarrier(); m_parent.CancellationToken = new CompatibleCancellationToken(m_tokenSource); m_parent.ProcessItem(m_item); } private void ProcessItems() { // In rare cases, this method was throwing a NullReferenceException which seems // to indicate that m_parent is null. At first glance, this should not be possible // as m_parent is readonly and the constructor is invoked by m_parent itself. // However, note that m_task is also assigned in the constructor by calling // Task.Factory.StartNew, which executes this method on a separate thread. If we // instead assume that the assignment operations in the constructor could be // reordered, or if this method executes on a separate processor before the first // processor's cache is flushed, then it should be possible for m_parent and even // m_items to be null. The memory barriers should prevent this reordering of events. Thread.MemoryBarrier(); m_parent.CancellationToken = new CompatibleCancellationToken(m_tokenSource); m_parent.ProcessItems(m_items); } // Blocks calling thread until specified process timeout has expired. private bool Wait() { if (m_task.Wait(m_parent.ProcessTimeout, m_tokenSource.Token)) return true; m_tokenSource.Cancel(); return false; } void IDisposable.Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { if ((object)m_task != null && (m_task.Status == TaskStatus.RanToCompletion || m_task.Status == TaskStatus.Faulted || m_task.Status == TaskStatus.Canceled)) m_task.Dispose(); m_tokenSource?.Dispose(); } } finally { m_disposed = true; // Prevent duplicate dispose. } } } public static bool Process(ProcessQueue parent, T item) { using (TemporalTask temporalProcess = new TemporalTask(parent, item)) { return temporalProcess.Wait(); } } public static bool Process(ProcessQueue parent, T[] items) { using (TemporalTask temporalProcess = new TemporalTask(parent, items)) { return temporalProcess.Wait(); } } } // Constants /// /// Default processing interval (in milliseconds). /// public const int DefaultProcessInterval = 100; /// /// Default maximum number of processing threads. /// public const int DefaultMaximumThreads = 5; /// /// Default processing timeout (in milliseconds). /// public const int DefaultProcessTimeout = Timeout.Infinite; /// /// Default setting for requeuing items on processing timeout. /// public const bool DefaultRequeueOnTimeout = false; /// /// Default setting for requeuing items on processing exceptions. /// public const bool DefaultRequeueOnException = false; /// /// Default real-time processing interval (in milliseconds). /// public const double RealTimeProcessInterval = 0.0; /// /// Default setting for requeuing mode on processing timeout. /// public const RequeueMode DefaultRequeueModeOnTimeout = RequeueMode.Prefix; /// /// Default setting for requeuing mode on processing exceptions. /// public const RequeueMode DefaultRequeueModeOnException = RequeueMode.Prefix; // Delegates /// /// Function signature that defines a method to process items one at a time. /// /// Item to be processed. /// /// Required unless is implemented. /// Creates an asynchronous to process individual items - one item at a time - on multiple threads. /// [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] public delegate void ProcessItemFunctionSignature(T item); /// /// Function signature that defines a method to process multiple items at once. /// /// Items to be processed. /// /// Required unless is implemented. /// Creates an asynchronous to process groups of items simultaneously on multiple threads. /// [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] public delegate void ProcessItemsFunctionSignature(T[] items); /// /// Function signature that determines if an item can be currently processed. /// /// Item to be checked for processing availability. /// True, if item can be processed. The default is true. /// /// Implementation of this function is optional. It is assumed that an item can be processed if this /// function is not defined /// Items must eventually get to a state where they can be processed, or they will remain in the /// indefinitely. /// /// Note that when this function is implemented and ProcessingStyle = ManyAtOnce (i.e., /// is defined), then each item presented for /// processing must evaluate as "CanProcessItem = True" before any items are processed. /// /// [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] public delegate bool CanProcessItemFunctionSignature(T item); // Events /// /// Event that is raised after an item has been successfully processed. /// /// /// Allows custom handling of successfully processed items. /// Allows notification when an item has completed processing in the allowed amount of time, if a process /// timeout is specified. /// Raised only when ProcessingStyle = OneAtATime (i.e., is defined). /// public event EventHandler> ItemProcessed; /// /// Event that is raised after an array of items have been successfully processed. /// /// /// Allows custom handling of successfully processed items. /// Allows notification when an item has completed processing in the allowed amount of time, if a process /// timeout is specified. /// Raised only when when ProcessingStyle = ManyAtOnce (i.e., is defined). /// public event EventHandler> ItemsProcessed; /// /// Event that is raised if an item's processing time exceeds the specified process timeout. /// /// /// Allows custom handling of items that took too long to process. /// Raised only when ProcessingStyle = OneAtATime (i.e., is defined). /// public event EventHandler> ItemTimedOut; /// /// Event that is raised if the processing time for an array of items exceeds the specified process timeout. /// /// /// Allows custom handling of items that took too long to process. /// Raised only when ProcessingStyle = ManyAtOnce (i.e., is defined). /// public event EventHandler> ItemsTimedOut; /// /// Event that is raised if an exception is encountered while attempting to processing an item in the . /// /// /// Processing will not stop for any exceptions thrown by the user function, but any captured exceptions will /// be exposed through this event. /// public event EventHandler> ProcessException; /// /// Occurs when the class has been disposed. /// public event EventHandler Disposed; // Fields private ProcessItemFunctionSignature m_processItemFunction; private ProcessItemsFunctionSignature m_processItemsFunction; private CanProcessItemFunctionSignature m_canProcessItemFunction; private ISynchronizedOperation m_synchronizedOperation; private SynchronizedOperationType m_synchronizedOperationType; private readonly ThreadLocal m_threadLocalCancellationToken; private IList m_processList; private int m_maximumThreads; private int m_processTimeout; private readonly bool m_processingIsRealTime; private bool m_requeueOnTimeout; private bool m_requeueOnException; private RequeueMode m_requeueModeOnTimeout; private RequeueMode m_requeueModeOnException; private volatile bool m_enabled; private int m_threadCount; private long m_itemsBeingProcessed; private long m_totalProcessedItems; private long m_totalFunctionCalls; private long m_startTime; private long m_stopTime; private string m_name; private bool m_disposed; private Timer m_processTimer; #endregion #region [ Constructors ] /// /// Creates a based on the generic List(Of T) class. /// /// Delegate that defines a method to process one item at a time. /// a value which represents the process interval in milliseconds. /// The maximum number of threads for the queue to use. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. public ProcessQueue(ProcessItemFunctionSignature processItemFunction, double processInterval = DefaultProcessInterval, int maximumThreads = DefaultMaximumThreads, int processTimeout = DefaultProcessTimeout, bool requeueOnTimeout = DefaultRequeueOnTimeout, bool requeueOnException = DefaultRequeueOnException) : this(processItemFunction, null, null, new List(), processInterval, maximumThreads, processTimeout, requeueOnTimeout, requeueOnException) { } /// /// Creates a based on the generic List(Of T) class. /// /// Delegate that defines a method to process one item at a time. /// Delegate that determines if an item can currently be processed. /// a value which represents the process interval in milliseconds. /// The maximum number of threads for the queue to use. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. public ProcessQueue(ProcessItemFunctionSignature processItemFunction, CanProcessItemFunctionSignature canProcessItemFunction = null, double processInterval = DefaultProcessInterval, int maximumThreads = DefaultMaximumThreads, int processTimeout = DefaultProcessTimeout, bool requeueOnTimeout = DefaultRequeueOnTimeout, bool requeueOnException = DefaultRequeueOnException) : this(processItemFunction, null, canProcessItemFunction, new List(), processInterval, maximumThreads, processTimeout, requeueOnTimeout, requeueOnException) { } /// /// Creates a bulk item based on the generic List(Of T) class. /// /// Delegate that defines a method to process multiple items at once. /// a value which represents the process interval in milliseconds. /// The maximum number of threads for the queue to use. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. public ProcessQueue(ProcessItemsFunctionSignature processItemsFunction, double processInterval = DefaultProcessInterval, int maximumThreads = DefaultMaximumThreads, int processTimeout = DefaultProcessTimeout, bool requeueOnTimeout = DefaultRequeueOnTimeout, bool requeueOnException = DefaultRequeueOnException) : this(null, processItemsFunction, null, new List(), processInterval, maximumThreads, processTimeout, requeueOnTimeout, requeueOnException) { } /// /// Creates a bulk item based on the generic List(Of T) class. /// /// Delegate that defines a method to process multiple items at once. /// Delegate that determines if an item can currently be processed. /// a value which represents the process interval in milliseconds. /// The maximum number of threads for the queue to use. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. public ProcessQueue(ProcessItemsFunctionSignature processItemsFunction, CanProcessItemFunctionSignature canProcessItemFunction = null, double processInterval = DefaultProcessInterval, int maximumThreads = DefaultMaximumThreads, int processTimeout = DefaultProcessTimeout, bool requeueOnTimeout = DefaultRequeueOnTimeout, bool requeueOnException = DefaultRequeueOnException) : this(null, processItemsFunction, canProcessItemFunction, new List(), processInterval, maximumThreads, processTimeout, requeueOnTimeout, requeueOnException) { } /// /// Allows derived classes to define their own instance, if desired. /// /// Delegate that defines a method to process one item at a time. /// Delegate that defines a method to process multiple items at once. /// Delegate that determines if an item can currently be processed. /// A storage list for items to be processed. /// a value which represents the process interval in milliseconds. /// The maximum number of threads for the queue to use. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. protected ProcessQueue(ProcessItemFunctionSignature processItemFunction, ProcessItemsFunctionSignature processItemsFunction, CanProcessItemFunctionSignature canProcessItemFunction, IList processList, double processInterval, int maximumThreads, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { if ((object)processList == null) throw new ArgumentNullException(nameof(processList), "ProcessQueue base list cannot be null"); m_processItemFunction = processItemFunction; // Defining this function creates a ProcessingStyle = OneAtATime process queue m_processItemsFunction = processItemsFunction; // Defining this function creates a ProcessingStyle = ManyAtOnce process queue m_canProcessItemFunction = canProcessItemFunction; m_threadLocalCancellationToken = new ThreadLocal(false); m_processList = processList; m_maximumThreads = maximumThreads; m_processTimeout = processTimeout; m_requeueOnTimeout = requeueOnTimeout; m_requeueOnException = requeueOnException; m_requeueModeOnTimeout = DefaultRequeueModeOnTimeout; m_requeueModeOnException = DefaultRequeueModeOnException; if (processInterval == RealTimeProcessInterval) { // Instantiates process queue for real-time item processing m_processingIsRealTime = true; m_maximumThreads = 1; } else { // Instantiates process queue for intervaled item processing m_processTimer = new Timer(); m_processTimer.Elapsed += ProcessTimerThreadProc; m_processTimer.Interval = processInterval; m_processTimer.AutoReset = true; m_processTimer.Enabled = false; } } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~ProcessQueue() { Dispose(false); } #endregion #region [ Properties ] /// /// Gets or sets the element at the specified index. /// /// The element at the specified index. /// The zero-based index of the element to get or set. /// index is less than 0 -or- index is equal to or greater than length. public virtual T this[int index] { get { lock (SyncRoot) { return InternalList[index]; } } set { lock (SyncRoot) { InternalList[index] = value; DataAdded(); } } } /// /// Attempts to remove and return an object from the . /// /// When this method returns, if the object was removed and returned successfully, item contains the removed object. If no object was available to be removed, the value is unspecified. /// true if an object was removed and returned successfully; otherwise, false. public bool TryTake(out T item) { lock (SyncRoot) { IList list = InternalList; if (list.Count > 0) { item = list[0]; list.RemoveAt(0); return true; } } item = default(T); return false; } /// /// Attempts to remove and return all objects from the . /// /// When this method returns, if any objects were removed and returned successfully, item array contains the removed objects. If no object was available to be removed, the value is null. /// true if any objects were removed and returned successfully; otherwise, false. public bool TryTake(out T[] items) { lock (SyncRoot) { if (Count > 0) { items = ToArray(); Clear(); return true; } } items = null; return false; } /// Gets a value indicating whether the is read-only. /// True, if the is read-only; otherwise, false. In the default implementation, this property /// always returns false. public virtual bool IsReadOnly { get { return InternalList.IsReadOnly; } } /// /// Gets or sets the user function for processing individual items in the one at a time. /// /// /// Cannot be defined simultaneously with . /// A must be defined to process a single item at a time or many items at once. /// Implementation makes ProcessingStyle = OneAtATime. /// public virtual ProcessItemFunctionSignature ProcessItemFunction { get { return m_processItemFunction; } set { if ((object)value != null) { m_processItemFunction = value; m_processItemsFunction = null; } } } /// /// Gets or sets the user function for processing multiple items in the at once. /// /// /// This function and cannot be defined at the same time /// A must be defined to process a single item at a time or many items at once /// Implementation of this function makes ProcessingStyle = ManyAtOnce /// public virtual ProcessItemsFunctionSignature ProcessItemsFunction { get { return m_processItemsFunction; } set { if ((object)value != null) { m_processItemsFunction = value; m_processItemFunction = null; } } } /// /// Gets or sets the user function determining if an item is ready to be processed. /// public virtual CanProcessItemFunctionSignature CanProcessItemFunction { get { return m_canProcessItemFunction; } set { m_canProcessItemFunction = value; } } /// /// Gets indicator that items will be processed in real-time. /// public virtual bool ProcessingIsRealTime { get { return m_processingIsRealTime; } } /// /// Gets the current for the (i.e., synchronous or asynchronous). /// /// /// The maximum number of processing threads determines the . /// If the maximum threads are set to one, item processing will be synchronous /// (i.e., ThreadingMode = Synchronous). /// If the maximum threads are more than one, item processing will be asynchronous /// (i.e., ThreadingMode = Asynchronous). /// /// Note that for asynchronous , the processing interval will control how many threads are spawned /// at once. If items are processed faster than the specified processing interval, only one process thread /// will ever be spawned at a time. To ensure multiple threads are utilized to items, lower /// the process interval (minimum process interval is 1 millisecond). /// /// public virtual QueueThreadingMode ThreadingMode { get { if (m_maximumThreads > 1) return QueueThreadingMode.Asynchronous; return QueueThreadingMode.Synchronous; } } /// /// Gets the item for the (i.e., one at a time or many at once). /// /// /// OneAtATime, if the is implemented. /// ManyAtOnce, if the is implemented. /// /// /// The implemented item processing function determines the . /// /// If the is ManyAtOnce, all available items in the are presented for processing /// at each processing interval. If you expect items to be processed in the order in which they were received, make /// sure you use a synchronous . Real-time are inherently synchronous. /// /// public virtual QueueProcessingStyle ProcessingStyle { get { if ((object)m_processItemFunction == null) return QueueProcessingStyle.ManyAtOnce; return QueueProcessingStyle.OneAtATime; } } /// /// Gets or sets the interval, in milliseconds, on which new items begin processing. /// public virtual double ProcessInterval { get { if (m_processingIsRealTime) return RealTimeProcessInterval; return m_processTimer.Interval; } set { if (m_processingIsRealTime) throw new InvalidOperationException("Cannot change process interval when " + Name + " is configured for real-time processing"); lock (SyncRoot) { m_processTimer.Interval = value; } } } /// /// Gets or sets the maximum number of threads to process simultaneously. /// /// Sets the maximum number of processing threads. /// Maximum number of processing threads. /// If MaximumThreads is set to one, item processing will be synchronous (i.e., ThreadingMode = Synchronous) public virtual int MaximumThreads { get { return m_maximumThreads; } set { if (m_processingIsRealTime) throw new InvalidOperationException("Cannot change the maximum number of threads when " + Name + " is configured for real-time processing"); m_maximumThreads = value; } } /// /// Gets or sets the maximum time, in milliseconds, allowed for processing an item. /// /// Sets the maximum number of milliseconds allowed to process an item. /// The maximum number of milliseconds allowed to process an item. /// Set to Timeout.Infinite (i.e., -1) to allow processing to take as long as needed. public virtual int ProcessTimeout { get { return m_processTimeout; } set { m_processTimeout = value; } } /// /// Gets or sets the type of synchronized operation used to process items in a real-time . /// public virtual SynchronizedOperationType SynchronizedOperationType { get { return m_synchronizedOperationType; } set { m_synchronizedOperationType = value; } } /// /// Gets or sets whether or not to automatically place an item back into the if the processing times out. /// /// Ignored if the ProcessTimeout is set to Timeout.Infinite (i.e., -1). public virtual bool RequeueOnTimeout { get { return m_requeueOnTimeout; } set { m_requeueOnTimeout = value; } } /// /// Gets or sets whether or not to automatically place an item back into the if an exception occurs /// while processing. /// public virtual bool RequeueOnException { get { return m_requeueOnException; } set { m_requeueOnException = value; } } /// /// Gets or sets the mode of insertion used (prefix or suffix) when at item is placed back into the /// after processing times out. /// /// Only relevant when RequeueOnTimeout = True. public virtual RequeueMode RequeueModeOnTimeout { get { return m_requeueModeOnTimeout; } set { m_requeueModeOnTimeout = value; } } /// /// Gets or sets the mode of insertion used (prefix or suffix) when at item is placed back into the /// after an exception occurs while processing. /// /// Only relevant when RequeueOnException = True. public virtual RequeueMode RequeueModeOnException { get { return m_requeueModeOnException; } set { m_requeueModeOnException = value; } } /// /// Gets or sets indicator that the is currently enabled. /// public virtual bool Enabled { get { return m_enabled; } set { lock (SyncRoot) { if (m_enabled && !value) Stop(); else if (!m_enabled && value) Start(); } } } /// /// Gets a flag that indicates whether the object has been disposed. /// public bool IsDisposed { get { return m_disposed; } } /// /// Gets indicator that the is actively processing items. /// public bool IsProcessing { get { if (m_processingIsRealTime) return ((object)m_synchronizedOperation != null); lock (SyncRoot) { // Enabled flag changes are always in a critical section to ensure all items will be processed return m_processTimer.Enabled; } } } /// /// Gets a value that indicates whether the is empty. /// public virtual bool IsEmpty { get { lock (SyncRoot) { return InternalList.Count == 0; } } } /// /// Gets the total number of items currently being processed. /// public long ItemsBeingProcessed { get { return m_itemsBeingProcessed; } } /// /// Gets the total number of items processed so far. /// public long TotalProcessedItems { get { return m_totalProcessedItems; } } /// /// Gets the total number of calls to or . /// public long TotalFunctionCalls { get { return m_totalFunctionCalls; } } /// /// Gets the current number of active threads. /// /// Current number of active threads. public int ThreadCount { get { return m_threadCount; } } /// /// Gets the total amount of time, in seconds, that the has been active. /// public virtual Time RunTime { get { Ticks processingTime = 0; if (m_startTime > 0) { if (m_stopTime > 0) processingTime = m_stopTime - m_startTime; else processingTime = DateTime.UtcNow.Ticks - m_startTime; } if (processingTime < 0) processingTime = 0; return processingTime.ToSeconds(); } } /// /// Gets or sets name for this . /// /// /// This name is used for class identification in strings (e.g., used in error messages). /// public virtual string Name { get { if (string.IsNullOrEmpty(m_name)) m_name = GetType().Name; return m_name; } set { m_name = value; } } /// Gets the number of elements actually contained in the . /// The number of elements actually contained in the . public virtual int Count { get { lock (SyncRoot) { return InternalList.Count; } } } /// Gets a value indicating whether access to the is synchronized (thread safe). Always returns true for . /// true, is always synchronized (thread safe). /// The is effectively "synchronized" since all functions SyncLock operations internally. public bool IsSynchronized { get { return true; } } /// /// Gets the per processing thread cancellation token to check when a is specified. /// /// /// This token should be checked in the user implemented or to /// determine if a timeout has occurred so that the code can cleanly exit. /// public ICancellationToken CancellationToken { get { return m_threadLocalCancellationToken.Value; } private set { m_threadLocalCancellationToken.Value = value; } } /// /// Gets the current run-time statistics of the as a single group of values. /// public virtual ProcessQueueStatistics CurrentStatistics { get { ProcessQueueStatistics statistics; statistics.IsEnabled = m_enabled; statistics.IsProcessing = IsProcessing; statistics.ProcessingInterval = ProcessInterval; statistics.ProcessingStyle = ProcessingStyle; statistics.ProcessTimeout = m_processTimeout; statistics.ThreadingMode = ThreadingMode; statistics.ActiveThreads = m_threadCount; statistics.ItemsBeingProcessed = m_itemsBeingProcessed; statistics.TotalProcessedItems = m_totalProcessedItems; statistics.QueueCount = Count; statistics.RunTime = RunTime; return statistics; } } /// /// Gets current status of . /// public virtual string Status { get { StringBuilder status = new StringBuilder(); status.AppendFormat(" Queue processing is: {0}", m_enabled ? "Enabled" : "Disabled"); status.AppendLine(); status.AppendFormat(" Current processing state: {0}", IsProcessing ? "Executing" : "Idle"); status.AppendLine(); status.Append(" Processing interval: "); if (ProcessingIsRealTime) { status.Append("Real-time"); } else { status.Append(ProcessInterval); status.Append(" milliseconds"); } status.AppendLine(); status.Append(" Processing timeout: "); if (ProcessTimeout == Timeout.Infinite) { status.Append("Infinite"); } else { status.Append(m_processTimeout); status.Append(" milliseconds"); } status.AppendLine(); status.Append(" Queue threading mode: "); if (ThreadingMode == QueueThreadingMode.Asynchronous) { status.Append("Asynchronous - "); status.Append(m_maximumThreads); status.Append(" maximum threads"); } else { status.Append("Synchronous"); } status.AppendLine(); status.Append(" Queue processing style: "); status.Append(ProcessingStyle == QueueProcessingStyle.OneAtATime ? "One at a time" : "Many at once"); status.AppendLine(); status.AppendFormat(" Requeue on exception: {0}", RequeueOnException); status.AppendLine(); if (RequeueOnException) { status.AppendFormat(" Requeue mode on exception: {0}", RequeueModeOnException); status.AppendLine(); } status.AppendFormat(" Requeue on time-out: {0}", RequeueOnTimeout); status.AppendLine(); if (RequeueOnTimeout) { status.AppendFormat(" Requeue mode on time-out: {0}", RequeueModeOnTimeout); status.AppendLine(); } status.AppendFormat(" Total process run time: {0}", RunTime.ToString(3)); status.AppendLine(); status.AppendFormat(" Total active threads: {0:N0}", m_threadCount); status.AppendLine(); status.AppendFormat(" Queued items to process: {0:N0}", Count); status.AppendLine(); status.AppendFormat(" Items being processed: {0:N0}", m_itemsBeingProcessed); status.AppendLine(); status.AppendFormat(" Total items processed: {0:N0}", m_totalProcessedItems); status.AppendLine(); status.AppendFormat(" Total function calls: {0:N0} to {1}", m_totalFunctionCalls, GetProcessingFunctionName()); status.AppendLine(); if (ProcessingStyle == QueueProcessingStyle.ManyAtOnce && m_totalFunctionCalls > 0) { status.AppendFormat(" Average items processed: {0:R} per function call", Math.Round(m_totalProcessedItems / (double)m_totalFunctionCalls, 2)); status.AppendLine(); } return status.ToString(); } } private string GetProcessingFunctionName() { string functionName = "undetermined"; if (ProcessingStyle == QueueProcessingStyle.OneAtATime) { if ((object)ProcessItemFunction != null && (object)ProcessItemFunction.Method != null) functionName = ProcessItemFunction.Method.Name.ToNonNullString(functionName); } else { if ((object)ProcessItemsFunction != null && (object)ProcessItemsFunction.Method != null) functionName = ProcessItemsFunction.Method.Name.ToNonNullString(functionName); } return functionName; } /// /// Allows derived classes to access the interfaced internal directly. /// protected IEnumerable InternalEnumerable { get { return m_processList; } } /// /// Gets an object that can be used to synchronize access to the . /// public object SyncRoot { get { return m_processList; } } /// /// Gets the internal list for direct use by . /// protected IList InternalList { get { return InternalEnumerable as IList; } } #endregion #region [ Methods ] /// /// Releases all the resources used by the object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!m_disposed) { try { // Must stop thread, otherwise your app will keep running :) Stop(); if (disposing) { if ((object)m_processTimer != null) { m_processTimer.Elapsed -= ProcessTimerThreadProc; m_processTimer.Dispose(); } m_threadLocalCancellationToken?.Dispose(); m_processTimer = null; m_processList = null; m_processItemFunction = null; m_processItemsFunction = null; m_canProcessItemFunction = null; } } finally { m_disposed = true; // Prevent duplicate dispose. if (Disposed != null) Disposed(this, EventArgs.Empty); } } } /// /// Manually signals that data has been modified and processing should resume. /// /// /// This function should be called in cases where a user may need to signal data modification. For example, /// if was a dictionary or list that was updated - you would need to manually /// signal that data had changed in this item. /// public virtual void SignalDataModified() { lock (SyncRoot) { DataAdded(); } } /// /// Determines whether an element is in the . /// /// True, if item is found in the ; otherwise, false. /// The object to locate in the . The value can be null for reference types. public virtual bool Contains(T item) { lock (SyncRoot) { return InternalList.Contains(item); } } /// /// Copies the elements contained in the to a new array. /// /// A new array containing the elements copied from the . public virtual T[] ToArray() { lock (SyncRoot) { return InternalList.ToArray(); } } /// /// Returns an enumerator that iterates through the . /// /// An enumerator for the . public virtual IEnumerator GetEnumerator() { return m_processList.GetEnumerator(); } /// /// Starts item processing. /// public virtual void Start() { m_enabled = true; m_threadCount = 0; m_totalProcessedItems = 0; m_stopTime = 0; m_startTime = DateTime.UtcNow.Ticks; if (m_processingIsRealTime) { // Start real-time processing thread switch (SynchronizedOperationType) { case SynchronizedOperationType.Short: m_synchronizedOperation = new ShortSynchronizedOperation(RealTimeThreadProc, OnProcessException); break; case SynchronizedOperationType.Long: m_synchronizedOperation = new LongSynchronizedOperation(RealTimeThreadProc, OnProcessException); break; case SynchronizedOperationType.LongBackground: m_synchronizedOperation = new LongSynchronizedOperation(RealTimeThreadProc, OnProcessException) { IsBackground = true }; break; case SynchronizedOperationType.Mixed: m_synchronizedOperation = new MixedSynchronizedOperation(RealTimeThreadProc, OnProcessException); break; case SynchronizedOperationType.DedicatedBackground: m_synchronizedOperation = new DedicatedSynchronizedOperation(RealTimeThreadProc, OnProcessException, true); break; case SynchronizedOperationType.DedicatedForeground: m_synchronizedOperation = new DedicatedSynchronizedOperation(RealTimeThreadProc, OnProcessException, false); break; } } else { // Start intervaled processing, if there items in the queue lock (SyncRoot) { // Enabled flag changes are always in a critical section to ensure all items will be processed m_processTimer.Enabled = Count > 0; } } } void ISupportLifecycle.Initialize() { // Enabled property handles check for redundant calls... Enabled = true; } /// /// Stops item processing. /// public virtual void Stop() { m_enabled = false; if (m_processingIsRealTime) { // Remove reference to synchronized operation - it will stop gracefully after it has finished processing // current set of items since enabled is false... m_synchronizedOperation = null; } else { // Stops intervaled processing, if active. if ((object)m_processTimer != null) { lock (SyncRoot) { // Enabled flag changes are always in a critical section to ensure all items will be processed m_processTimer.Enabled = false; } } } m_stopTime = DateTime.UtcNow.Ticks; } /// /// Blocks the current thread, if the is active (i.e., user has called "Start" method), until all items /// in are processed, and then stops the . /// /// /// /// Begins processing items as quickly as possible, regardless of currently defined process interval, until all /// items in the have been processed. Stops the when this function ends. /// This method is typically called on shutdown to make sure any remaining queued items get processed before the /// is destructed. /// /// /// It is possible for items to be added to the while the flush is executing. The flush will continue to /// process items as quickly as possible until the is empty. Unless the user stops queuing items to be /// processed, the flush call may never return (not a happy situation on shutdown). For this reason, during this /// function call, requeuing of items on exception or process timeout is temporarily disabled. /// /// /// The does not clear queue prior to destruction. If the user fails to call this method before the /// class is destructed, there may be items that remain unprocessed in the . /// /// public virtual void Flush() { bool enabled = m_enabled; // Stop all queue processing... Stop(); if (enabled) { bool originalRequeueOnTimeout = m_requeueOnTimeout; bool originalRequeueOnException = m_requeueOnException; // We must disable requeuing of items or this method could continue indefinitely. m_requeueOnTimeout = false; m_requeueOnException = false; // Only waits around if there is something to process. while (Count > 0) { // Create a real-time processing loop that will process remaining items as quickly as possible. while (!IsEmpty) { if ((object)m_processItemsFunction == null) { // Processes one item at a time. ProcessNextItem(); } else { // Processes multiple items at once. ProcessNextItems(); } } } // Just in case user continues to use queue after flush, this restores original states. m_requeueOnTimeout = originalRequeueOnTimeout; m_requeueOnException = originalRequeueOnException; } } #region [ Item Processing Functions ] /// /// Raises the base class event. /// /// /// Derived classes cannot raise events of their base classes, so we expose event wrapper methods to accommodate /// as needed. /// /// A generic type T to be passed to ItemProcessed. protected virtual void OnItemProcessed(T item) { if ((object)ItemProcessed != null) ItemProcessed(this, new EventArgs(item)); } /// /// Raises the base class event. /// /// /// Derived classes cannot raise events of their base classes, so we expose event wrapper methods to accommodate /// as needed. /// /// An array of generic type T to be passed to ItemsProcessed. protected virtual void OnItemsProcessed(T[] items) { if ((object)ItemsProcessed != null) ItemsProcessed(this, new EventArgs(items)); } /// /// Raises the base class event. /// /// /// Derived classes cannot raise events of their base classes, so we expose event wrapper methods to accommodate /// as needed. /// /// A generic type T to be passed to ItemProcessed. protected virtual void OnItemTimedOut(T item) { if ((object)ItemTimedOut != null) ItemTimedOut(this, new EventArgs(item)); } /// /// Raises the base class event. /// /// /// Derived classes cannot raise events of their base classes, so we expose event wrapper methods to accommodate /// as needed. /// /// An array of generic type T to be passed to ItemsProcessed. protected virtual void OnItemsTimedOut(T[] items) { if ((object)ItemsTimedOut != null) ItemsTimedOut(this, new EventArgs(items)); } /// /// Raises the base class event. /// /// /// Derived classes cannot raise events of their base classes, so we expose event wrapper methods to accommodate /// as needed. /// /// to be passed to ProcessException. protected virtual void OnProcessException(Exception ex) { if ((object)ProcessException != null) ProcessException(this, new EventArgs(ex)); } /// /// Notifies queue that data was added and/or modified, so it can begin processing data. /// /// /// /// Derived classes *must* make sure to call this method after data gets added, so that the /// process timer can be enabled for intervaled and data processing can begin. /// /// /// To make sure items in the always get processed, this function is expected to be /// invoked from within a SyncLock of the exposed SyncRoot (i.e., InternalList). /// /// protected virtual void DataAdded() { if (m_processingIsRealTime) { if ((object)m_synchronizedOperation != null) m_synchronizedOperation.RunOnceAsync(); } else { lock (SyncRoot) { // Enabled flag changes are always in a critical section to ensure all items will be processed if (m_enabled && !m_processTimer.Enabled) m_processTimer.Enabled = true; } } } /// /// Determines if an item can be processed. /// /// True, if user provided no implementation for the CanProcessItemFunction. /// /// /// Use this function instead of invoking the CanProcessItemFunction pointer /// directly, since implementation of this delegate is optional. /// /// /// The item T to process. /// A value indicating whether it can process the item or not. protected virtual bool CanProcessItem(T item) { // If user provided no implementation for this function or function failed, we assume item can be processed. if ((object)m_canProcessItemFunction == null) return true; try { // When user function is provided, we call it to determine if item state allows processing at this time. return m_canProcessItemFunction(item); } catch (ThreadAbortException) { // Rethrow thread abort so calling method can respond appropriately throw; } catch (Exception ex) { // Processing will not stop for any errors thrown by the user function, but errors will be reported. OnProcessException(ex); } // Assuming processing must go on if the user function fails return true; } /// /// Determines if all items can be processed. /// /// True, if user provided no implementation for the CanProcessItemFunction. /// /// /// Use this function instead of invoking the CanProcessItemFunction pointer /// directly, since implementation of this delegate is optional. /// /// /// An array of items of type T. /// A value indicating whether the process queue can process the items. protected virtual bool CanProcessItems(T[] items) { // If user provided no implementation for this function or function failed, we assume item can be processed. if ((object)m_canProcessItemFunction == null) return true; // Otherwise we call user function for each item to determine if all items are ready for processing. return items.All(CanProcessItem); } /// /// Requeues item into according to specified requeue reason. /// /// A generic item of type T to be requeued. /// The reason the object is being requeued. protected virtual void RequeueItem(T item, RequeueReason reason) { RequeueMode mode; switch (reason) { case RequeueReason.Exception: mode = RequeueModeOnException; break; case RequeueReason.Timeout: mode = RequeueModeOnTimeout; break; default: mode = RequeueMode.Prefix; break; } if (mode == RequeueMode.Prefix) { Insert(0, item); } else { Add(item); } } /// /// Requeues items into according to specified requeue reason. /// /// Array of type T to be requeued. /// The reason the object is being requeued. protected virtual void RequeueItems(T[] items, RequeueReason reason) { RequeueMode mode; switch (reason) { case RequeueReason.Exception: mode = RequeueModeOnException; break; case RequeueReason.Timeout: mode = RequeueModeOnTimeout; break; default: mode = RequeueMode.Prefix; break; } if (mode == RequeueMode.Prefix) { InsertRange(0, items); } else { AddRange(items); } } #region [ Handy List(Of T) Functions Implementation ] // The internal list is declared as an IList(Of T). Derived classes (e.g., ProcessDictionary) can use their own // list implementation for process functionality. However, the regular List(Of T) provides many handy functions // that are not required to be exposed by the IList(Of T) interface. So, if the implemented list is a List(Of T), // we'll expose this native functionality; otherwise, we implement it for you. Yeah, you'll thank me one day. // Note: All List(Of T) implementations should be synchronized, as necessary. /// /// Adds the elements of the specified collection to the end of the . /// /// /// The collection whose elements should be added to the end of the . /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type. /// /// collection is null. public void AddRange(IEnumerable collection) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature if process queue is not a List(Of T). if ((object)collection == null) throw new ArgumentNullException(nameof(collection), "collection is null"); foreach (T item in collection) { InternalList.Add(item); } } else { // Otherwise, we'll call native implementation. processList.AddRange(collection); } DataAdded(); } } /// /// Searches the entire sorted , using a binary search algorithm, for an element using the /// default comparer and returns the zero-based index of the element. /// /// /// must be sorted in order for this function to return an accurate result. /// /// The object to locate. The value can be null for reference types. /// /// The zero-based index of item in the sorted , if item is found; otherwise, a negative number that is the /// bitwise complement of the index of the next element that is larger than item or, if there is no larger element, /// the bitwise complement of count. /// /// The default comparer, Generic.Comparer.Default, cannot find an /// implementation of the IComparable generic interface or the IComparable interface for type T. public virtual int BinarySearch(T item) { return BinarySearch(0, InternalList.Count, item, null); } /// /// Searches the entire sorted , using a binary search algorithm, for an element using the /// specified comparer and returns the zero-based index of the element. /// /// /// must be sorted in order for this function to return an accurate result. /// /// The object to locate. The value can be null for reference types. /// The Generic.IComparer implementation to use when comparing elements -or- /// null to use the default comparer: Generic.Comparer(Of T).Default /// /// The zero-based index of item in the sorted , if item is found; otherwise, a negative number that is the /// bitwise complement of the index of the next element that is larger than item or, if there is no larger element, /// the bitwise complement of count. /// /// The default comparer, Generic.Comparer.Default, cannot find an /// implementation of the IComparable generic interface or the IComparable interface for type T. public virtual int BinarySearch(T item, IComparer comparer) { return BinarySearch(0, InternalList.Count, item, comparer); } /// /// Searches a range of elements in the sorted , using a binary search algorithm, for an /// element using the specified comparer and returns the zero-based index of the element. /// /// /// must be sorted in order for this function to return an accurate result. /// /// The zero-based starting index of the range to search. /// The length of the range to search. /// The object to locate. The value can be null for reference types. /// The Generic.IComparer implementation to use when comparing elements -or- null to use /// the default comparer: Generic.Comparer(Of T).Default /// /// The zero-based index of item in the sorted , if item is found; otherwise, a negative number that is the /// bitwise complement of the index of the next element that is larger than item or, if there is no larger element, /// the bitwise complement of count. /// /// startIndex is outside the range of valid indexes for the /// -or- count is less than 0 -or- startIndex and count do not specify a valid section in the /// The default comparer, Generic.Comparer.Default, cannot find an /// implementation of the IComparable generic interface or the IComparable interface for type T. public virtual int BinarySearch(int index, int count, T item, IComparer comparer) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). int foundIndex = -1; int startIndex = index; int stopIndex = index + count - 1; int currentIndex; int result; // Validates start and stop index. if (startIndex < 0 || count < 0 || stopIndex > InternalList.Count - 1) throw new ArgumentOutOfRangeException(nameof(index), "index and/or count is outside the range of valid indexes for the queue"); if ((object)comparer == null) comparer = Comparer.Default; if (count > 0) { while (true) { // Finds next mid point. currentIndex = startIndex + (stopIndex - startIndex) / 2; // Compares item at mid-point result = comparer.Compare(item, InternalList[currentIndex]); if (result == 0) { // For a found item, returns located index. foundIndex = currentIndex; break; } if (startIndex == stopIndex) { // Met in the middle and didn't find match, so we are finished, foundIndex = startIndex ^ -1; break; } if (result > 0) { if (currentIndex < count - 1) { // Item is beyond current item, so we start search at next item. startIndex = currentIndex + 1; } else { // Looked to the end and did not find match, so we are finished. foundIndex = (count - 1) ^ -1; break; } } else { if (currentIndex > 0) { // Item is before current item, so we will stop search at current item. // Note that because of the way the math works, you do not stop at the // prior item, as you might guess. It can cause you to skip an item. stopIndex = currentIndex; } else { // Looked to the top and did not find match, so we are finished. foundIndex = 0 ^ -1; break; } } } } return foundIndex; } // Otherwise, we will call native implementation. return processList.BinarySearch(index, count, item, comparer); } } /// Converts the elements in the current to another type, and returns a containing the /// converted elements. /// A generic list of the target type containing the converted elements from the current . /// A Converter delegate that converts each element from one type to another type. /// converter is null. /// The generic type used. public virtual List ConvertAll(Converter converter) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if ((object)converter == null) throw new ArgumentNullException(nameof(converter), "converter is null"); return InternalList.Select(item => converter(item)).ToList(); } // Otherwise, we will call native implementation return processList.ConvertAll(converter); } } /// Determines whether the contains elements that match the conditions defined by the specified /// predicate. /// True, if the contains one or more elements that match the conditions defined by the specified /// predicate; otherwise, false. /// The Predicate delegate that defines the conditions of the elements to search for. /// match is null. public virtual bool Exists(Predicate match) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if ((object)match == null) throw new ArgumentNullException(nameof(match), "match is null"); return InternalList.Any(t => match(t)); } // Otherwise, we will call native implementation. return processList.Exists(match); } } /// Searches for an element that matches the conditions defined by the specified predicate, and returns /// the first occurrence within the entire . /// The first element that matches the conditions defined by the specified predicate, if found; /// otherwise, the default value for type T. /// The Predicate delegate that defines the conditions of the element to search for. /// match is null. public virtual T Find(Predicate match) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if ((object)match == null) throw new ArgumentNullException(nameof(match), "match is null"); T foundItem = default(T); int foundIndex = FindIndex(match); if (foundIndex >= 0) foundItem = InternalList[foundIndex]; return foundItem; } // Otherwise, we will call native implementation. return processList.Find(match); } } /// Retrieves all elements that match the conditions defined by the specified predicate. /// A generic list containing all elements that match the conditions defined by the specified predicate, /// if found; otherwise, an empty list. /// The Predicate delegate that defines the conditions of the elements to search for. /// match is null. public virtual List FindAll(Predicate match) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if ((object)match == null) throw new ArgumentNullException(nameof(match), "match is null"); return InternalList.Where(item => match(item)).ToList(); } // Otherwise, we will call native implementation. return processList.FindAll(match); } } /// Searches for an element that matches the conditions defined by the specified predicate, and returns /// the zero-based index of the first occurrence within the range of elements in the that extends from the /// specified index to the last element. /// The zero-based index of the first occurrence of an element that matches the conditions defined by /// match, if found; otherwise, –1. /// The Predicate delegate that defines the conditions of the element to search for. /// match is null. public virtual int FindIndex(Predicate match) { return FindIndex(0, InternalList.Count, match); } /// Searches for an element that matches the conditions defined by the specified predicate, and returns /// the zero-based index of the first occurrence within the range of elements in the that extends from the /// specified index to the last element. /// The zero-based index of the first occurrence of an element that matches the conditions defined by /// match, if found; otherwise, –1. /// The zero-based starting index of the search. /// The Predicate delegate that defines the conditions of the element to search for. /// startIndex is outside the range of valid indexes for the . /// match is null. public virtual int FindIndex(int startIndex, Predicate match) { return FindIndex(startIndex, InternalList.Count, match); } /// Searches for an element that matches the conditions defined by the specified predicate, and returns /// the zero-based index of the first occurrence within the range of elements in the that extends from the /// specified index to the last element. /// The zero-based index of the first occurrence of an element that matches the conditions defined by /// match, if found; otherwise, –1. /// The zero-based starting index of the search. /// The number of elements in the section to search. /// The Predicate delegate that defines the conditions of the element to search for. /// startIndex is outside the range of valid indexes for the /// -or- count is less than 0 -or- startIndex and count do not specify a valid section in the . /// match is null. public virtual int FindIndex(int startIndex, int count, Predicate match) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if (startIndex < 0 || count < 0 || startIndex + count > InternalList.Count) throw new ArgumentOutOfRangeException(nameof(startIndex), "startIndex and/or count is outside the range of valid indexes for the queue"); if ((object)match == null) throw new ArgumentNullException(nameof(match), "match is null"); int foundindex = -1; for (int x = startIndex; x < startIndex + count; x++) { if (match(InternalList[x])) { foundindex = x; break; } } return foundindex; } // Otherwise, we will call native implementation. return processList.FindIndex(startIndex, count, match); } } /// Searches for an element that matches the conditions defined by the specified predicate, and returns the last occurrence within the entire . /// The last element that matches the conditions defined by the specified predicate, if found; otherwise, the default value for type T. /// The Predicate delegate that defines the conditions of the element to search for. /// match is null. public virtual T FindLast(Predicate match) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature if process queue is not a List(Of T) if ((object)match == null) throw (new ArgumentNullException(nameof(match), "match is null")); T foundItem = default(T); int foundIndex = FindLastIndex(match); if (foundIndex >= 0) foundItem = InternalList[foundIndex]; return foundItem; } // Otherwise, we will call native implementation. return processList.FindLast(match); } } /// Searches for an element that matches the conditions defined by the specified predicate, and returns /// the zero-based index of the last occurrence within the entire . /// The zero-based index of the last occurrence of an element that matches the conditions defined by /// match, if found; otherwise, –1. /// The Predicate delegate that defines the conditions of the element to search for. /// match is null. public virtual int FindLastIndex(Predicate match) { return FindLastIndex(0, InternalList.Count, match); } /// Searches for an element that matches the conditions defined by the specified predicate, and returns /// the zero-based index of the last occurrence within the range of elements in the that extends from the /// first element to the specified index. /// The zero-based index of the last occurrence of an element that matches the conditions defined by /// match, if found; otherwise, –1. /// The zero-based starting index of the backward search. /// The Predicate delegate that defines the conditions of the element to search for. /// startIndex is outside the range of valid indexes for the . /// match is null. public virtual int FindLastIndex(int startIndex, Predicate match) { return FindLastIndex(startIndex, InternalList.Count, match); } /// Searches for an element that matches the conditions defined by the specified predicate, and returns /// the zero-based index of the last occurrence within the range of elements in the that contains the /// specified number of elements and ends at the specified index. /// The zero-based index of the last occurrence of an element that matches the conditions defined by /// match, if found; otherwise, –1. /// The number of elements in the section to search. /// The zero-based starting index of the backward search. /// The Predicate delegate that defines the conditions of the element to search for. /// startIndex is outside the range of valid indexes for the /// -or- count is less than 0 -or- startIndex and count do not specify a valid section in the . /// match is null. public virtual int FindLastIndex(int startIndex, int count, Predicate match) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if (startIndex < 0 || count < 0 || startIndex + count > InternalList.Count) throw new ArgumentOutOfRangeException(nameof(startIndex), "startIndex and/or count is outside the range of valid indexes for the queue"); if ((object)match == null) throw new ArgumentNullException(nameof(match), "match is null"); int foundindex = -1; for (int x = startIndex + count - 1; x >= startIndex; x--) { if (match(InternalList[x])) { foundindex = x; break; } } return foundindex; } // Otherwise, we will call native implementation. return processList.FindLastIndex(startIndex, count, match); } } /// Performs the specified action on each element of the . /// The Action delegate to perform on each element of the . /// action is null. public virtual void ForEach(Action action) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if ((object)action == null) throw new ArgumentNullException(nameof(action), "action is null"); foreach (T item in InternalList) { action(item); } } else { // Otherwise, we will call native implementation. processList.ForEach(action); } } } /// Creates a shallow copy of a range of elements in the source . /// A shallow copy of a range of elements in the source . /// The number of elements in the range. /// The zero-based index at which the range starts. /// index is less than 0 -or- count is less than 0. /// index and count do not denote a valid range of elements in the . public virtual List GetRange(int index, int count) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if (index + count > InternalList.Count) throw new ArgumentException("Index and count do not denote a valid range of elements in the queue"); if (index < 0 || count < 0) throw new ArgumentOutOfRangeException(nameof(index), "Index and/or count is outside the range of valid indexes for the queue"); List items = new List(); for (int x = index; x < index + count; x++) { items.Add(InternalList[x]); } return items; } // Otherwise, we will call native implementation. return processList.GetRange(index, count); } } /// Searches for the specified object and returns the zero-based index of the first occurrence within /// the range of elements in the that extends from the specified index to the last element. /// The zero-based index of the first occurrence of item within the range of elements in the that /// extends from index to the last element, if found; otherwise, –1. /// The object to locate in the . The value can be null for reference types. /// The zero-based starting index of the search. /// index is outside the range of valid indexes for the . public virtual int IndexOf(T item, int index) { return IndexOf(item, index, InternalList.Count); } /// Searches for the specified object and returns the zero-based index of the first occurrence within /// the range of elements in the that starts at the specified index and contains the specified number of /// elements. /// The zero-based index of the first occurrence of item within the range of elements in the that /// starts at index and contains count number of elements, if found; otherwise, –1. /// The number of elements in the section to search. /// The object to locate in the . The value can be null for reference types. /// The zero-based starting index of the search. /// index is outside the range of valid indexes for the /// -or- count is less than 0 -or- index and count do not specify a valid section in the . public virtual int IndexOf(T item, int index, int count) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if (index < 0 || count < 0 || index + count > InternalList.Count) throw new ArgumentOutOfRangeException(nameof(index), "Index and/or count is outside the range of valid indexes for the queue"); int foundindex = -1; Comparer comparer = Comparer.Default; for (int x = index; x < index + count; x++) { if (comparer.Compare(item, InternalList[x]) == 0) { foundindex = x; break; } } return foundindex; } // Otherwise, we will call native implementation. return processList.IndexOf(item, index, count); } } /// Inserts the elements of a collection into the at the specified index. /// The collection whose elements should be inserted into the . The collection /// itself cannot be null, but it can contain elements that are null, if type T is a reference type. /// The zero-based index at which the new elements should be inserted. /// index is less than 0 -or- index is greater than length. /// collection is null. public virtual void InsertRange(int index, IEnumerable collection) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if (index < 0 || index > InternalList.Count - 1) throw new ArgumentOutOfRangeException(nameof(index), "index is outside the range of valid indexes for the queue"); if ((object)collection == null) throw new ArgumentNullException(nameof(collection), "collection is null"); foreach (T item in collection) { InternalList.Insert(index, item); index++; } } else { // Otherwise, we will call native implementation. processList.InsertRange(index, collection); } DataAdded(); } } /// Searches for the specified object and returns the zero-based index of the last occurrence within the /// entire . /// The zero-based index of the last occurrence of item within the entire the , if found; /// otherwise, –1. /// The object to locate in the . The value can be null for reference types. public virtual int LastIndexOf(T item) { return LastIndexOf(item, 0, InternalList.Count); } /// Searches for the specified object and returns the zero-based index of the last occurrence within the /// range of elements in the that extends from the first element to the specified index. /// The zero-based index of the last occurrence of item within the range of elements in the that /// extends from the first element to index, if found; otherwise, –1. /// The object to locate in the . The value can be null for reference types. /// The zero-based starting index of the backward search. /// index is outside the range of valid indexes for the . public virtual int LastIndexOf(T item, int index) { return LastIndexOf(item, index, InternalList.Count); } /// Searches for the specified object and returns the zero-based index of the last occurrence within the /// range of elements in the that contains the specified number of elements and ends at the specified index. /// The zero-based index of the last occurrence of item within the range of elements in the that /// contains count number of elements and ends at index, if found; otherwise, –1. /// The object to locate in the . The value can be null for reference types. /// The zero-based starting index of the backward search. /// The number of elements in the section to search. /// index is outside the range of valid indexes for the -or- /// count is less than 0 -or- index and count do not specify a valid section in the . public virtual int LastIndexOf(T item, int index, int count) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if (index < 0 || count < 0 || index + count > InternalList.Count) throw new ArgumentOutOfRangeException(nameof(index), "Index and/or count is outside the range of valid indexes for the queue"); int foundindex = -1; Comparer comparer = Comparer.Default; for (int x = index + count - 1; x >= index; x--) { if (comparer.Compare(item, InternalList[x]) == 0) { foundindex = x; break; } } return foundindex; } // Otherwise, we'll call native implementation. return processList.LastIndexOf(item, index, count); } } /// Removes the all the elements that match the conditions defined by the specified predicate. /// The number of elements removed from the . /// The Predicate delegate that defines the conditions of the elements to remove. /// match is null. public virtual int RemoveAll(Predicate match) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if ((object)match == null) throw new ArgumentNullException(nameof(match), "match is null"); int removedItems = 0; // Process removal from the bottom up to maintain proper index access for (int x = InternalList.Count - 1; x >= 0; x--) { if (match(InternalList[x])) { InternalList.RemoveAt(x); removedItems++; } } return removedItems; } // Otherwise, we will call native implementation. return processList.RemoveAll(match); } } /// Removes a range of elements from the . /// The number of elements to remove. /// The zero-based starting index of the range of elements to remove. /// index is less than 0 -or- count is less than 0. /// index and count do not denote a valid range of elements in the . public virtual void RemoveRange(int index, int count) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if (index < 0 || count < 0 || index + count > InternalList.Count) throw new ArgumentOutOfRangeException(nameof(index), "Index and/or count is outside the range of valid indexes for the queue"); for (int x = index + count - 1; x >= index; x--) { InternalList.RemoveAt(x); } } else { // Otherwise, we will call native implementation. processList.RemoveRange(index, count); } } } /// Reverses the order of the elements in the entire . public virtual void Reverse() { Reverse(0, InternalList.Count); } /// Reverses the order of the elements in the specified range. /// The number of elements in the range to reverse. /// The zero-based starting index of the range to reverse. /// index and count do not denote a valid range of elements in the . /// index is less than 0 -or- count is less than 0. public virtual void Reverse(int index, int count) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if (index + count > InternalList.Count) throw new ArgumentException("Index and count do not denote a valid range of elements in the queue"); if (index < 0 || count < 0) throw new ArgumentOutOfRangeException(nameof(index), "Index and/or count is outside the range of valid indexes for the queue"); T item; int stopIndex = index + count - 1; for (int x = index; x < (index + count) / 2; x++) { if (x < stopIndex) { // Swaps items top to bottom to reverse order. item = InternalList[x]; InternalList[x] = InternalList[stopIndex]; InternalList[stopIndex] = item; stopIndex--; } } } else { // Otherwise, we will call native implementation. processList.Reverse(index, count); } } } /// Sorts the elements in the entire , using the default comparer. /// The default comparer, Generic.Comparer.Default, cannot find an /// implementation of the IComparable generic interface or the IComparable interface for type T. public virtual void Sort() { Sort(0, InternalList.Count, null); } /// Sorts the elements in the entire , using the specified comparer. /// The Generic.IComparer implementation to use when comparing elements, or null to use /// the default comparer: Generic.Comparer.Default. /// The implementation of comparer caused an error during the sort. For /// example, comparer might not return 0 when comparing an item with itself. /// the comparer is null and the default comparer, /// Generic.Comparer.Default, cannot find an implementation of the IComparable generic interface or the /// IComparable interface for type T. public virtual void Sort(IComparer comparer) { Sort(0, InternalList.Count, comparer); } /// Sorts the elements in a range of elements in the , using the specified comparer. /// The length of the range to sort. /// The zero-based starting index of the range to sort. /// The Generic.IComparer implementation to use when comparing elements, or null to use /// the default comparer: Generic.Comparer.Default. /// The implementation of comparer caused an error during the sort. For /// example, comparer might not return 0 when comparing an item with itself. /// the comparer is null and the default comparer, /// Generic.Comparer.Default, cannot find an implementation of the IComparable generic interface or the /// IComparable interface for type T. /// index is less than 0 -or- count is less than 0. public virtual void Sort(int index, int count, IComparer comparer) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if ((object)comparer == null) comparer = Comparer.Default; // This sort implementation is a little harsh, but the normal process queue uses List(Of T) and the // keyed process queue is based on a sorted list anyway (i.e., no sorting needed); so, this alternate // sort implementation exists for any future derived process queue possibly based on a non List(Of T) // queue and will at least ensure that the function will perform as expected. T[] items = ToArray(); Array.Sort(items, index, count, comparer); InternalList.Clear(); AddRange(items); } else { // Otherwise, we will call native implementation. processList.Sort(index, count, comparer); } } } /// Sorts the elements in the entire , using the specified comparison. /// The comparison to use when comparing elements. /// The implementation of comparison caused an error during the sort. For /// example, comparison might not return 0 when comparing an item with itself. /// comparison is null. public virtual void Sort(Comparison comparison) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if ((object)comparison == null) throw new ArgumentNullException(nameof(comparison), "comparison is null"); // This sort implementation is a little harsh, but the normal process queue uses List(Of T) and the // keyed process queue is based on a sorted list anyway (i.e., no sorting needed); so, this alternate // sort implementation exists for any future derived process queue possibly based on a non-List(Of T) // queue and will at least ensure that the function will perform as expected. Maybe some clever // programmer will come behind me and add some "Linq-y" expression that will magically do this... T[] items = ToArray(); Array.Sort(items, comparison); InternalList.Clear(); AddRange(items); } else { // Otherwise we'll call native implementation processList.Sort(comparison); } } } /// Determines whether every element in the matches the conditions defined by the specified /// predicate. /// True, if every element in the matches the conditions defined by the specified predicate; /// otherwise, false. If the has no elements, the return value is true. /// The Predicate delegate that defines the conditions to check against the elements. /// match is null. public virtual bool TrueForAll(Predicate match) { lock (SyncRoot) { List processList = InternalList as List; if ((object)processList == null) { // We manually implement this feature, if process queue is not a List(Of T). if ((object)match == null) throw (new ArgumentNullException(nameof(match), "match is null")); return InternalList.All(item => match(item)); } // Otherwise, we will call native implementation. return processList.TrueForAll(match); } } #endregion #region [ Generic IList(Of T) Implementation ] // Note: All IList(Of T) implementations should be synchronized, as necessary. IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)InternalList).GetEnumerator(); } /// Adds an item to the . /// The item to add to the . public void Add(T item) { lock (SyncRoot) { InternalList.Add(item); DataAdded(); } } /// Inserts an element into the at the specified index. /// The object to insert. The value can be null for reference types. /// The zero-based index at which item should be inserted. /// index is less than 0 -or- index is greater than length. public virtual void Insert(int index, T item) { lock (SyncRoot) { InternalList.Insert(index, item); DataAdded(); } } /// Copies the entire to a compatible one-dimensional array, starting at the beginning of the /// target array. /// The one-dimensional array that is the destination of the elements copied from . The /// array must have zero-based indexing. /// The zero-based index in array at which copying begins. /// arrayIndex is equal to or greater than the length of array -or- the /// number of elements in the source is greater than the available space from arrayIndex to the end of the /// destination array. /// arrayIndex is less than 0. /// array is null. public void CopyTo(T[] array, int arrayIndex) { lock (SyncRoot) { InternalList.CopyTo(array, arrayIndex); } } /// Searches for the specified object and returns the zero-based index of the first occurrence within /// the entire . /// The zero-based index of the first occurrence of item within the entire , if found; otherwise, –1. /// The object to locate in the . The value can be null for reference types. public virtual int IndexOf(T item) { lock (SyncRoot) { return InternalList.IndexOf(item); } } /// /// Removes all elements from the . /// public void Clear() { lock (SyncRoot) { InternalList.Clear(); } } /// Removes the first occurrence of a specific object from the . /// True, if item is successfully removed; otherwise, false. This method also returns false if item was /// not found in the . /// The object to remove from the . The value can be null for reference types. public virtual bool Remove(T item) { lock (SyncRoot) { return InternalList.Remove(item); } } /// Removes the element at the specified index of the . /// The zero-based index of the element to remove. /// index is less than 0 -or- index is equal to or greater than /// length. public virtual void RemoveAt(int index) { lock (SyncRoot) { InternalList.RemoveAt(index); } } #endregion /// /// Handles standard processing of a single item. /// /// A generic item of type T to be processed. private void ProcessItem(T item) { try { // Invokes user function to process item. m_processItemFunction(item); Interlocked.Increment(ref m_totalProcessedItems); Interlocked.Increment(ref m_totalFunctionCalls); // Notifies consumers of successfully processed items. OnItemProcessed(item); } catch (ThreadAbortException) { // Rethrows thread abort, so calling method can respond appropriately. throw; } catch (Exception ex) { // Requeues item on processing exception, if requested. if (m_requeueOnException) RequeueItem(item, RequeueReason.Exception); // Processing will not stop for any errors thrown by the user function, but errors will be reported. OnProcessException(ex); } } /// /// Handles standard processing of multiple items. /// /// Array of type T. private void ProcessItems(T[] items) { try { // Invokes user function to process items. m_processItemsFunction(items); Interlocked.Add(ref m_totalProcessedItems, items.Length); Interlocked.Increment(ref m_totalFunctionCalls); // Notifies consumers of successfully processed items. OnItemsProcessed(items); } catch (ThreadAbortException) { // Rethrows thread abort, so calling method can respond appropriately. throw; } catch (Exception ex) { // Requeues items on processing exception, if requested. if (m_requeueOnException) RequeueItems(items, RequeueReason.Exception); // Processing will not stop for any errors thrown by the user function, but errors will be reported. OnProcessException(ex); } } // Processes items private void RealTimeThreadProc() { if ((object)m_processItemsFunction == null) ProcessNextItem(); // Process one item at a time. else ProcessNextItems(); // Process multiple items at once. if (Count > 0 && (object)m_synchronizedOperation != null) m_synchronizedOperation.RunOnceAsync(); } /// /// Processes queued items on an interval. /// /// The sender object of the item. /// Arguments for the elapsed event. private void ProcessTimerThreadProc(object sender, ElapsedEventArgs e) { // The system timer creates an intervaled processing loop such that if an existing item processing // call hasn't completed before next interval, multiple processing calls will be spawned thereby // distributing item processing across multiple threads as needed. if ((object)m_processItemsFunction == null) ProcessNextItem(); // Process one item at a time. else ProcessNextItems(); // Process multiple items at once. if ((object)m_processTimer != null) { // Stop the process timer if there is no more data to process. lock (SyncRoot) { // Enabled flag changes are always in a critical section to ensure all items will be processed if (IsEmpty) m_processTimer.Enabled = false; else if (m_enabled && !m_processTimer.Enabled) m_processTimer.Enabled = true; } } } /// /// Processes next item in queue, one at a time (i.e., ProcessingStyle = OneAtATime). /// private void ProcessNextItem() { T nextItem = default(T); bool processingItem = false; try { // Get the next item to be processed if the number of current process threads is less // than the maximum allowable number of process threads. if (m_threadCount < m_maximumThreads && TryTake(out nextItem)) { // Call optional user function to see if we can process this item. if (CanProcessItem(nextItem)) { Interlocked.Increment(ref m_threadCount); Interlocked.Increment(ref m_itemsBeingProcessed); processingItem = true; } else { // If item state is not ready for processing, all we can do is requeue. RequeueItem(nextItem, RequeueReason.CannotProcess); } } if (processingItem) { if (m_processTimeout == Timeout.Infinite) { // If an item is in the queue to process, and the process queue was not set up with a process // timeout, we use the current thread (i.e., the timer event or real-time thread) to process the // next item taking as long as we need for it to complete. For timer events, the next item in // the queue will begin processing even if this item is not completed, but no more than the // specified number of maximum threads will ever be spawned at once. ProcessItem(nextItem); } else { // If an item is in the queue to process, with a specified process timeout, a new thread is // created to handle the processing. The timer event or real-time thread that invoked this method // is already a new thread, so the only reason to create another thread is to implement the // process timeout if the process takes too long to run. This is done by joining the current // thread (which will block it) until the specified interval has passed or the process thread // completes, whichever comes first. This is a safe operation since the current thread // (i.e., the timer event or real-time thread) was already an independent thread and will not // block any other processing, including another timer event. if (!TemporalTask.Process(this, nextItem)) { // Notify user of process timeout, in case they want to do anything special. OnItemTimedOut(nextItem); // Requeues item on processing timeout, if requested. if (m_requeueOnTimeout) RequeueItem(nextItem, RequeueReason.Timeout); } } } } catch (ThreadAbortException) { // Rethrows thread abort, so calling method can respond appropriately. throw; } catch (Exception ex) { // Processing will not stop for any errors encountered here, but errors will be reported. OnProcessException(ex); } finally { // Decrements thread count, if item was retrieved for processing. if (processingItem) { Interlocked.Decrement(ref m_threadCount); Interlocked.Decrement(ref m_itemsBeingProcessed); } } } /// /// Processes next items in an array of items as a group (i.e., ProcessingStyle = ManyAtOnce). /// private void ProcessNextItems() { T[] nextItems = null; bool processingItems = false; try { // Get next items to be processed if the number of current process threads is less // than the maximum allowable number of process threads. if (m_threadCount < m_maximumThreads && TryTake(out nextItems)) { // Call optional user function to see if these items can be processed. if (CanProcessItems(nextItems)) { Interlocked.Increment(ref m_threadCount); Interlocked.Add(ref m_itemsBeingProcessed, nextItems.Length); processingItems = true; } else { // If item state is not ready for processing, all we can do is requeue. RequeueItems(nextItems, RequeueReason.CannotProcess); } } if (processingItems) { if (m_processTimeout == Timeout.Infinite) { // If items are in the queue to process, and the process queue was not set up with a process // timeout, the current thread (i.e., the timer event or real-time thread) is used to process the // next items taking as long as necessary to complete. For timer events, any new items available // in the queue will be processed, even if the current items have not completed, but no more than // the specified number of maximum threads will ever be spawned at once. ProcessItems(nextItems); } else { // If items are in the queue to process, and a process timeout was specified, a new thread is // created to handle the processing. The timer event or real-time thread that invoked this method // is already a new thread, so the only reason to create another thread is to implement the // process timeout if the process takes too long to run. We do this by joining the current thread // (which will block it) until the specified interval has passed or the process thread completes, // whichever comes first. This is a safe operation, since the current thread (i.e., the timer // event or real-time thread) was already an independent thread and will not block any other // processing, including another timer event. if (!TemporalTask.Process(this, nextItems)) { // Notify the user of the process timeout, in case they want to do anything special. OnItemsTimedOut(nextItems); // Requeues items on processing timeout, if requested. if (m_requeueOnTimeout) RequeueItems(nextItems, RequeueReason.Timeout); } } } } catch (ThreadAbortException) { // Rethrows thread abort, so calling method can respond appropriately. throw; } catch (Exception ex) { // Processing will not stop for any errors encountered here, but errors will be reported. OnProcessException(ex); } finally { // Decrements thread count, if items were retrieved for processing. if (processingItems) { Interlocked.Decrement(ref m_threadCount); Interlocked.Add(ref m_itemsBeingProcessed, -nextItems.Length); } } } #endregion #endregion #region [ Static ] #region [ Single-Item Processing Constructors ] /// /// Creates a new asynchronous with the default settings: ProcessInterval = 100, MaximumThreads = 5, /// ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that processes one item at a time. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemFunctionSignature processItemFunction) { return CreateAsynchronousQueue(processItemFunction, null, DefaultProcessInterval, DefaultMaximumThreads, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new asynchronous with the default settings: ProcessInterval = 100, MaximumThreads = 5, /// ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that processes one item at a time. /// Delegate which determines whether an item can be processed. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemFunctionSignature processItemFunction, CanProcessItemFunctionSignature canProcessItemFunction) { return CreateAsynchronousQueue(processItemFunction, canProcessItemFunction, DefaultProcessInterval, DefaultMaximumThreads, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new asynchronous with the default settings: ProcessInterval = 100, /// ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that processes one item at a time. /// An value that determines the maximum number of threads used to process items. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemFunctionSignature processItemFunction, int maximumThreads) { return CreateAsynchronousQueue(processItemFunction, null, DefaultProcessInterval, maximumThreads, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new asynchronous with the default settings: ProcessInterval = 100, /// ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that processes one item at a time. /// Delegate which determines whether an item can be processed. /// An value that determines the maximum number of threads used to process items. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemFunctionSignature processItemFunction, CanProcessItemFunctionSignature canProcessItemFunction, int maximumThreads) { return CreateAsynchronousQueue(processItemFunction, canProcessItemFunction, DefaultProcessInterval, maximumThreads, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new asynchronous using specified settings. /// /// Delegate that processes one item at a time. /// a value which represents the process interval in milliseconds. /// An value that determines the maximum number of threads used to process items. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemFunctionSignature processItemFunction, double processInterval, int maximumThreads, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return CreateAsynchronousQueue(processItemFunction, null, processInterval, maximumThreads, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new asynchronous using specified settings. /// /// Delegate that processes one item at a time. /// Delegate which determines whether an item can be processed. /// a value which represents the process interval in milliseconds. /// An value that determines the maximum number of threads used to process items. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemFunctionSignature processItemFunction, CanProcessItemFunctionSignature canProcessItemFunction, double processInterval, int maximumThreads, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return new ProcessQueue(processItemFunction, canProcessItemFunction, processInterval, maximumThreads, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new synchronous (i.e., single process thread) with the default settings: /// ProcessInterval = 100, ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that processes one item at a time. /// A ProcessQueue object based on type T. public static ProcessQueue CreateSynchronousQueue(ProcessItemFunctionSignature processItemFunction) { return CreateSynchronousQueue(processItemFunction, null, DefaultProcessInterval, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new synchronous (i.e., single process thread) with the default settings: /// ProcessInterval = 100, ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that processes one item at a time. /// Delegate which determines whether an item can be processed. /// A ProcessQueue object based on type T. public static ProcessQueue CreateSynchronousQueue(ProcessItemFunctionSignature processItemFunction, CanProcessItemFunctionSignature canProcessItemFunction) { return CreateSynchronousQueue(processItemFunction, canProcessItemFunction, DefaultProcessInterval, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new synchronous (i.e., single process thread) using specified settings. /// /// Delegate that processes one item at a time. /// a value which represents the process interval in milliseconds. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateSynchronousQueue(ProcessItemFunctionSignature processItemFunction, double processInterval, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return CreateSynchronousQueue(processItemFunction, null, processInterval, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new synchronous (i.e., single process thread) using specified settings. /// /// Delegate that processes one item at a time. /// Delegate which determines whether an item can be processed. /// a value which represents the process interval in milliseconds. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateSynchronousQueue(ProcessItemFunctionSignature processItemFunction, CanProcessItemFunctionSignature canProcessItemFunction, double processInterval, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return new ProcessQueue(processItemFunction, canProcessItemFunction, processInterval, 1, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new real-time with the default settings: ProcessTimeout = Infinite, /// RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that processes one item at a time. /// A ProcessQueue object based on type T. public static ProcessQueue CreateRealTimeQueue(ProcessItemFunctionSignature processItemFunction) { return CreateRealTimeQueue(processItemFunction, null, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new real-time with the default settings: ProcessTimeout = Infinite, /// RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that processes one item at a time. /// Delegate which determines whether an item can be processed. /// A ProcessQueue object based on type T. public static ProcessQueue CreateRealTimeQueue(ProcessItemFunctionSignature processItemFunction, CanProcessItemFunctionSignature canProcessItemFunction) { return CreateRealTimeQueue(processItemFunction, canProcessItemFunction, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new real-time using specified settings. /// /// Delegate that processes one item at a time. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateRealTimeQueue(ProcessItemFunctionSignature processItemFunction, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return CreateRealTimeQueue(processItemFunction, null, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new real-time using specified settings. /// /// Delegate that processes one item at a time. /// Delegate which determines whether an item can be processed. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateRealTimeQueue(ProcessItemFunctionSignature processItemFunction, CanProcessItemFunctionSignature canProcessItemFunction, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return new ProcessQueue(processItemFunction, canProcessItemFunction, RealTimeProcessInterval, 1, processTimeout, requeueOnTimeout, requeueOnException); } #endregion #region [ Multi-Item Processing Constructors ] /// /// Creates a new asynchronous, bulk item with the default settings: ProcessInterval = 100, /// MaximumThreads = 5, ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that defines a method to process multiple items at once. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemsFunctionSignature processItemsFunction) { return CreateAsynchronousQueue(processItemsFunction, null, DefaultProcessInterval, DefaultMaximumThreads, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new asynchronous, bulk item with the default settings: ProcessInterval = 100, /// MaximumThreads = 5, ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that defines a method to process multiple items at once. /// Delegate which determines whether an item can be processed. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemsFunctionSignature processItemsFunction, CanProcessItemFunctionSignature canProcessItemFunction) { return CreateAsynchronousQueue(processItemsFunction, canProcessItemFunction, DefaultProcessInterval, DefaultMaximumThreads, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new asynchronous, bulk item with the default settings: ProcessInterval = 100, /// ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that defines a method to process multiple items at once. /// An value that determines the maximum number of threads used to process items. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemsFunctionSignature processItemsFunction, int maximumThreads) { return CreateAsynchronousQueue(processItemsFunction, null, DefaultProcessInterval, maximumThreads, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new asynchronous, bulk item with the default settings: ProcessInterval = 100, /// ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that defines a method to process multiple items at once. /// Delegate which determines whether an item can be processed. /// An value that determines the maximum number of threads used to process items. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemsFunctionSignature processItemsFunction, CanProcessItemFunctionSignature canProcessItemFunction, int maximumThreads) { return CreateAsynchronousQueue(processItemsFunction, canProcessItemFunction, DefaultProcessInterval, maximumThreads, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new asynchronous, bulk item using specified settings. /// /// Delegate that defines a method to process multiple items at once. /// a value which represents the process interval in milliseconds. /// An value that determines the maximum number of threads used to process items. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemsFunctionSignature processItemsFunction, double processInterval, int maximumThreads, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return CreateAsynchronousQueue(processItemsFunction, null, processInterval, maximumThreads, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new asynchronous, bulk item using specified settings. /// /// Delegate that defines a method to process multiple items at once. /// Delegate which determines whether an item can be processed. /// a value which represents the process interval in milliseconds. /// An value that determines the maximum number of threads used to process items. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateAsynchronousQueue(ProcessItemsFunctionSignature processItemsFunction, CanProcessItemFunctionSignature canProcessItemFunction, double processInterval, int maximumThreads, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return new ProcessQueue(processItemsFunction, canProcessItemFunction, processInterval, maximumThreads, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new synchronous, bulk item (i.e., single process thread) with the default settings: /// ProcessInterval = 100, ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that defines a method to process multiple items at once. /// A ProcessQueue object based on type T. public static ProcessQueue CreateSynchronousQueue(ProcessItemsFunctionSignature processItemsFunction) { return CreateSynchronousQueue(processItemsFunction, null, DefaultProcessInterval, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new synchronous, bulk item (i.e., single process thread) with the default settings: /// ProcessInterval = 100, ProcessTimeout = Infinite, RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that defines a method to process multiple items at once. /// Delegate which determines whether an item can be processed. /// A ProcessQueue object based on type T. public static ProcessQueue CreateSynchronousQueue(ProcessItemsFunctionSignature processItemsFunction, CanProcessItemFunctionSignature canProcessItemFunction) { return CreateSynchronousQueue(processItemsFunction, canProcessItemFunction, DefaultProcessInterval, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new synchronous, bulk item (i.e., single process thread) using specified settings. /// /// Delegate that defines a method to process multiple items at once. /// a value which represents the process interval in milliseconds. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateSynchronousQueue(ProcessItemsFunctionSignature processItemsFunction, double processInterval, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return CreateSynchronousQueue(processItemsFunction, null, processInterval, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new synchronous, bulk item (i.e., single process thread) using specified settings. /// /// Delegate that defines a method to process multiple items at once. /// Delegate which determines whether an item can be processed. /// a value which represents the process interval in milliseconds. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateSynchronousQueue(ProcessItemsFunctionSignature processItemsFunction, CanProcessItemFunctionSignature canProcessItemFunction, double processInterval, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return new ProcessQueue(processItemsFunction, canProcessItemFunction, processInterval, 1, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new real-time, bulk item with the default settings: ProcessTimeout = Infinite, /// RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that defines a method to process multiple items at once. /// A ProcessQueue object based on type T. public static ProcessQueue CreateRealTimeQueue(ProcessItemsFunctionSignature processItemsFunction) { return CreateRealTimeQueue(processItemsFunction, null, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new real-time, bulk item with the default settings: ProcessTimeout = Infinite, /// RequeueOnTimeout = False, RequeueOnException = False. /// /// Delegate that defines a method to process multiple items at once. /// Delegate which determines whether an item can be processed. /// A ProcessQueue object based on type T. public static ProcessQueue CreateRealTimeQueue(ProcessItemsFunctionSignature processItemsFunction, CanProcessItemFunctionSignature canProcessItemFunction) { return CreateRealTimeQueue(processItemsFunction, canProcessItemFunction, DefaultProcessTimeout, DefaultRequeueOnTimeout, DefaultRequeueOnException); } /// /// Creates a new real-time, bulk item using specified settings. /// /// Delegate that defines a method to process multiple items at once. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateRealTimeQueue(ProcessItemsFunctionSignature processItemsFunction, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return CreateRealTimeQueue(processItemsFunction, null, processTimeout, requeueOnTimeout, requeueOnException); } /// /// Creates a new real-time, bulk item using specified settings. /// /// Delegate that defines a method to process multiple items at once. /// Delegate which determines whether an item can be processed. /// The number of seconds before a process should timeout. /// A value that indicates whether a process should requeue an item on timeout. /// A value that indicates whether a process should requeue after an exception. /// A ProcessQueue object based on type T. public static ProcessQueue CreateRealTimeQueue(ProcessItemsFunctionSignature processItemsFunction, CanProcessItemFunctionSignature canProcessItemFunction, int processTimeout, bool requeueOnTimeout, bool requeueOnException) { return new ProcessQueue(processItemsFunction, canProcessItemFunction, RealTimeProcessInterval, 1, processTimeout, requeueOnTimeout, requeueOnException); } #endregion #endregion } }