412 lines · 11.6 KB · C#
RawDownload
//******************************************************************************************************//  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{    /// <summary>    /// A thread-safe double-buffered queue that allows for low-contention    /// item processing in single-producer, single-consumer scenarios.    /// </summary>    /// <typeparam name="T">Type of items being queued.</typeparam>    /// <remarks>    /// It is not safe to use this class with multiple consumer threads.    /// The <see cref="Dequeue"/> 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.    /// </remarks>    public class DoubleBufferedQueue<T>    {        #region [ Members ]         // Events         /// <summary>        /// Event that is raised if an exception is encountered while attempting to processing an item in the <see cref="AsyncQueue{T}"/>.        /// </summary>        /// <remarks>        /// Processing will not stop for any exceptions thrown by user processing function, but exceptions will be exposed through this event.        /// </remarks>        public event EventHandler<EventArgs<Exception>> ProcessException;         // Fields        private int m_listIndex;        private readonly List<T>[] m_lists;#if MONO        private readonly object m_swapLock;#else        private SpinLock m_swapLock;#endif        private int m_count;         private Action<IList<T>> m_processItemsFunction;        private readonly ISynchronizedOperation m_processItemsOperation;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new instance of the <see cref="DoubleBufferedQueue{T}"/> class.        /// </summary>        public DoubleBufferedQueue()        {            m_lists = new List<T>[2];            m_lists[0] = new List<T>();            m_lists[1] = new List<T>();#if MONO            m_swapLock = new object();#else            m_swapLock = new SpinLock();#endif            m_processItemsOperation = new ShortSynchronizedOperation(TryProcessItems, OnProcessException);        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets item processing function.        /// </summary>        public Action<IList<T>> ProcessItemsFunction        {            get            {                return Interlocked.CompareExchange(ref m_processItemsFunction, null, null);            }            set            {                Interlocked.Exchange(ref m_processItemsFunction, value);            }        }         /// <summary>        /// Gets the current number of items in the queue.        /// </summary>        public int Count        {            get            {                return Interlocked.CompareExchange(ref m_count, 0, 0);            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Enqueues a collection of items into the double-buffered queue.        /// </summary>        /// <param name="items">The collection of items to be enqueued.</param>        public void Enqueue(IEnumerable<T> 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();        }         /// <summary>        /// Dequeues a collection of items from the queue.        /// </summary>        /// <returns>        /// A collection of items that have previously been enqueued,        /// or no items if none have been enqueued since last dequeue.        /// </returns>        public IList<T> 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        }         /// <summary>        /// Empties the producer's buffer so that the        /// items can no longer be consumed by the consumer.        /// </summary>        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        }         /// <summary>        /// Attempts to enqueue a collection of items into the double-buffered queue.        /// </summary>        /// <param name="items">The collection of items to be enqueued.</param>        /// <returns>        /// True if the items were successfully enqueued; false otherwise.        /// </returns>        public bool TryEnqueue(IEnumerable<T> 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;        }         /// <summary>        /// Attempts to dequeue a collection of items from the queue and        /// returns the number of items left in the queue after dequeuing.        /// </summary>        /// <param name="items">The items that were dequeued.</param>        /// <returns>        /// The number of items left in the queue after        /// dequeuing as many items as possible.        /// </returns>        public int TryDequeue(out IList<T> 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        }         /// <summary>        /// Attempts to enqueue a collection of items into the double-buffered queue.        /// </summary>        /// <returns>        /// True if the items were successfully enqueued; false otherwise.        /// </returns>        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<T> 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<Exception>(ex));        }         #endregion         #region [ Static ]         // Static Fields        private static readonly IList<T> EmptyList = new T[0];         #endregion    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t