//****************************************************************************************************** // DoubleBufferedQueue.cs - Gbtc // // Copyright © 2013, 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: // ---------------------------------------------------------------------------------------------------- // 06/13/2013 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Threading; using GSF.Threading; namespace GSF.Collections { /// /// A thread-safe double-buffered queue that allows for low-contention /// item processing in single-producer, single-consumer scenarios. /// /// Type of items being queued. /// /// It is not safe to use this class with multiple consumer threads. /// The method must be called by one thread at /// a time, and the consumer must not access a list returned by Dequeue /// after its next call to Dequeue. /// public class DoubleBufferedQueue { #region [ Members ] // Events /// /// 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 user processing function, but exceptions will be exposed through this event. /// public event EventHandler> ProcessException; // Fields private int m_listIndex; private readonly List[] m_lists; #if MONO private readonly object m_swapLock; #else private SpinLock m_swapLock; #endif private int m_count; private Action> m_processItemsFunction; private readonly ISynchronizedOperation m_processItemsOperation; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public DoubleBufferedQueue() { m_lists = new List[2]; m_lists[0] = new List(); m_lists[1] = new List(); #if MONO m_swapLock = new object(); #else m_swapLock = new SpinLock(); #endif m_processItemsOperation = new ShortSynchronizedOperation(TryProcessItems, OnProcessException); } #endregion #region [ Properties ] /// /// Gets or sets item processing function. /// public Action> ProcessItemsFunction { get { return Interlocked.CompareExchange(ref m_processItemsFunction, null, null); } set { Interlocked.Exchange(ref m_processItemsFunction, value); } } /// /// Gets the current number of items in the queue. /// public int Count { get { return Interlocked.CompareExchange(ref m_count, 0, 0); } } #endregion #region [ Methods ] /// /// Enqueues a collection of items into the double-buffered queue. /// /// The collection of items to be enqueued. public void Enqueue(IEnumerable items) { #if MONO lock (m_swapLock) { #else bool lockTaken = false; try { m_swapLock.Enter(ref lockTaken); #endif m_lists[m_listIndex].AddRange(items); m_count = m_lists[m_listIndex].Count; } #if !MONO finally { if (lockTaken) m_swapLock.Exit(); } #endif if ((object)ProcessItemsFunction != null) m_processItemsOperation.RunOnceAsync(); } /// /// Dequeues a collection of items from the queue. /// /// /// A collection of items that have previously been enqueued, /// or no items if none have been enqueued since last dequeue. /// public IList Dequeue() { if (m_count == 0) return EmptyList; int listIndex; #if MONO lock (m_swapLock) { #else bool lockTaken = false; try { m_swapLock.Enter(ref lockTaken); #endif listIndex = m_listIndex; m_listIndex = 1 - m_listIndex; m_lists[m_listIndex].Clear(); m_count = 0; return m_lists[listIndex]; } #if !MONO finally { if (lockTaken) m_swapLock.Exit(); } #endif } /// /// Empties the producer's buffer so that the /// items can no longer be consumed by the consumer. /// public void Clear() { #if MONO lock (m_swapLock) { #else bool lockTaken = false; try { m_swapLock.Enter(ref lockTaken); #endif m_lists[m_listIndex].Clear(); m_count = 0; } #if !MONO finally { if (lockTaken) m_swapLock.Exit(); } #endif } /// /// Attempts to enqueue a collection of items into the double-buffered queue. /// /// The collection of items to be enqueued. /// /// True if the items were successfully enqueued; false otherwise. /// public bool TryEnqueue(IEnumerable items) { bool lockTaken = false; #if MONO if (Monitor.TryEnter(m_swapLock)) { try { lockTaken = true; m_lists[m_listIndex].AddRange(items); m_count = m_lists[m_listIndex].Count; } finally { Monitor.Exit(m_swapLock); } } #else try { m_swapLock.TryEnter(ref lockTaken); if (lockTaken) { m_lists[m_listIndex].AddRange(items); m_count = m_lists[m_listIndex].Count; } } finally { if (lockTaken) m_swapLock.Exit(); } #endif if (lockTaken && (object)ProcessItemsFunction != null) m_processItemsOperation.RunOnceAsync(); return lockTaken; } /// /// Attempts to dequeue a collection of items from the queue and /// returns the number of items left in the queue after dequeuing. /// /// The items that were dequeued. /// /// The number of items left in the queue after /// dequeuing as many items as possible. /// public int TryDequeue(out IList items) { if (m_count == 0) { items = EmptyList; return 0; } int listIndex; #if MONO if (Monitor.TryEnter(m_swapLock)) { try { listIndex = m_listIndex; m_listIndex = 1 - m_listIndex; m_lists[m_listIndex].Clear(); m_count = 0; items = m_lists[listIndex]; return 0; } finally { Monitor.Exit(m_swapLock); } } items = EmptyList; return m_count; #else bool lockTaken = false; try { m_swapLock.TryEnter(ref lockTaken); if (lockTaken) { listIndex = m_listIndex; m_listIndex = 1 - m_listIndex; m_lists[m_listIndex].Clear(); m_count = 0; items = m_lists[listIndex]; } else { items = EmptyList; } return m_count; } finally { if (lockTaken) m_swapLock.Exit(); } #endif } /// /// Attempts to enqueue a collection of items into the double-buffered queue. /// /// /// True if the items were successfully enqueued; false otherwise. /// public bool TryClear() { bool lockTaken = false; #if MONO if (Monitor.TryEnter(m_swapLock)) { try { lockTaken = true; m_lists[m_listIndex].Clear(); m_count = 0; } finally { Monitor.Exit(m_swapLock); } } #else try { m_swapLock.TryEnter(ref lockTaken); if (lockTaken) { m_lists[m_listIndex].Clear(); m_count = 0; } } finally { if (lockTaken) m_swapLock.Exit(); } #endif return lockTaken; } // Attempts to dequeue and process items from the queue. private void TryProcessItems() { try { IList items = Dequeue(); if (items.Count > 0) m_processItemsFunction(items); } catch (Exception ex) { OnProcessException(ex); } } // Raises the ProcessException event. private void OnProcessException(Exception ex) { if ((object)ProcessException != null) ProcessException(this, new EventArgs(ex)); } #endregion #region [ Static ] // Static Fields private static readonly IList EmptyList = new T[0]; #endregion } }