417 lines · 14.5 KB · C#
RawDownload
//******************************************************************************************************//  IsolatedQueue.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://  ----------------------------------------------------------------------------------------------------//  1/4/2013 - Steven E. Chisholm//       Generated original version of source code. ////****************************************************************************************************** using System;using System.Collections.Concurrent;using System.Runtime.CompilerServices;using System.Threading; namespace GSF.Collections{    /// <summary>    /// Provides a buffer of point data where reads are isolated from writes.    /// However, reads must be synchronized with other reads and writes must be synchronized with other writes.    /// </summary>    /// <remarks>    /// This class is about twice as fast as a ConcurrentQueue. However, it has some decent overhead, so     /// small lists may not make much sense.    ///     /// This also uses jagged arrays, and will automatically deallocate memory so is useful for     /// structures that vary in size with time.     /// </remarks>    public class IsolatedQueue<T>    {        /// <summary>        /// Represents an individual node that allows for items to be added and removed from the         /// queue independently and without locks.         /// </summary>        class IsolatedNode        {            private readonly int m_lastBlock;            private volatile int m_tail;            private volatile int m_head;            private readonly T[] m_blocks;             /// <summary>            /// Creates a <see cref="IsolatedNode"/>            /// </summary>            /// <param name="count">the number of items in each node.</param>            public IsolatedNode(int count)            {                m_tail = 0;                m_head = 0;                m_blocks = new T[count];                m_lastBlock = m_blocks.Length;            }             /// <summary>            /// Gets if the current node is out of entries.            /// </summary>            public bool DequeueMustMoveToNextNode            {                get                {                    return m_tail == m_lastBlock;                }            }             /// <summary>            /// Gets if there are items that can be dequeued            /// </summary>            public bool CanDequeue            {                get                {                    return m_head != m_tail;                }            }             /// <summary>            /// Gets if this list can be enqueued            /// </summary>            public bool CanEnqueue            {                get                {                    return m_head != m_lastBlock;                }            }             /// <summary>            /// Gets the number of items that can be enqueued.            /// </summary>            public int AvailableEnqueueLength            {                get                {                    return m_lastBlock - m_head;                }            }             /// <summary>            /// Resets the queue. This operation must be synchronized external             /// from this class with the read and write operations. Therefore it             /// is only recommended to call this once the item has been returned to a             /// buffer pool of some sorts.            /// </summary>            public void Reset()            {                if (m_tail != m_head)                    Array.Clear(m_blocks, m_tail, m_head - m_tail);                m_tail = 0;                m_head = 0;            }             /// <summary>            /// Adds the following item to the queue. Be sure to check if it is full first.            /// </summary>            /// <param name="item"></param>            [MethodImpl(MethodImplOptions.AggressiveInlining)]            public void Enqueue(T item)            {                m_blocks[m_head] = item;                //No memory barior here since .NET 2.0 ensures that writes will not be reordered.                m_head = m_head + 1;            }             /// <summary>            /// Adds the following items to the queue. Be sure to check if it is full first.             /// Adjust the length if not the right size.            /// </summary>            /// <param name="items"></param>            /// <param name="offset"></param>            /// <param name="length"></param>            [MethodImpl(MethodImplOptions.AggressiveInlining)]            public void Enqueue(T[] items, int offset, int length)            {                Array.Copy(items, offset, m_blocks, m_head, length);                //No memory barior here since .NET 2.0 ensures that writes will not be reordered.                m_head = m_head + length;            }             [MethodImpl(MethodImplOptions.AggressiveInlining)]            public T Dequeue()            {                T item = m_blocks[m_tail];                m_blocks[m_tail] = default(T);                //No memory barior here since .NET 2.0 ensures that writes will not be reordered.                m_tail = m_tail + 1;                return item;            }             [MethodImpl(MethodImplOptions.NoInlining)]            public int Dequeue(T[] items, ref int startingIndex, ref int length)            {                int count = Math.Min(m_head - m_tail, length);                Array.Copy(m_blocks, m_tail, items, startingIndex, count);                Array.Clear(m_blocks, m_tail, count);                m_tail += count;                startingIndex += count;                length -= count;                return count;            }        }          private readonly ConcurrentQueue<IsolatedNode> m_blocks;        private readonly ConcurrentQueue<IsolatedNode> m_pooledNodes;         private IsolatedNode m_currentHead;        private IsolatedNode m_currentTail;         private readonly int m_unitCount;        private long m_enqueueCount;        private long m_dequeueCount;         /// <summary>        /// Creates an <see cref="IsolatedQueue{T}"/>        /// </summary>        public IsolatedQueue()        {            m_unitCount = 128;            m_pooledNodes = new ConcurrentQueue<IsolatedNode>();            m_blocks = new ConcurrentQueue<IsolatedNode>();        }         /// <summary>        /// The number of elements in the queue        /// </summary>        public long Count        {            get            {                //ToDo: Figure out how to make this value always correct on a 32-bit computer.                return m_enqueueCount - m_dequeueCount;            }        }         /// <summary>        /// Addes the provided items to the <see cref="IsolatedQueue{T}"/>.        /// </summary>        /// <param name="items">the items to add</param>        /// <param name="offset">the offset postion</param>        /// <param name="length">the length</param>        [MethodImpl(MethodImplOptions.NoInlining)]        public void Enqueue(T[] items, int offset, int length)        {            items.ValidateParameters(offset, length);            while (true)            {                //If the header is empty. Get a new one.                if (m_currentHead == null || !m_currentHead.CanEnqueue)                {                    m_currentHead = GetNode();                    m_currentHead.Reset();                    Thread.MemoryBarrier();                    m_blocks.Enqueue(m_currentHead);                    m_enqueueCount++;                }                 //If the remainder will fit, queue it all.                 int enqueueLength = m_currentHead.AvailableEnqueueLength;                if (length <= enqueueLength)                {                    m_currentHead.Enqueue(items, offset, length);                    m_enqueueCount += length;                    return;                }                 //Some will not fit. Enqueue what will fit, then repeat.                m_currentHead.Enqueue(items, offset, enqueueLength);                m_enqueueCount += enqueueLength;                offset += enqueueLength;                length -= enqueueLength;            }        }         /// <summary>        /// Addes the provided item to the <see cref="IsolatedQueue{T}"/>.        /// </summary>        /// <param name="item"></param>        [MethodImpl(MethodImplOptions.NoInlining)]        public void Enqueue(T item)        {            if (m_currentHead != null && m_currentHead.CanEnqueue)            {                m_currentHead.Enqueue(item);                m_enqueueCount++;                return;            }            EnqueueSlower(item);        }         [MethodImpl(MethodImplOptions.NoInlining)]        void EnqueueSlower(T item)        {            if (m_currentHead == null || !m_currentHead.CanEnqueue)            {                m_currentHead = GetNode();                m_currentHead.Reset();                Thread.MemoryBarrier();                m_blocks.Enqueue(m_currentHead);                m_enqueueCount++;            }            m_currentHead.Enqueue(item);        }         /// <summary>        /// Attempts to dequeue the specified item from the <see cref="IsolatedQueue{T}"/>        /// </summary>        /// <param name="item">an output for the item</param>        /// <returns></returns>        [MethodImpl(MethodImplOptions.NoInlining)]        public bool TryDequeue(out T item)        {            if (m_currentTail != null && m_currentTail.CanDequeue)            {                item = m_currentTail.Dequeue();                m_dequeueCount++;                return true;            }            return TryDequeueSlower(out item);        }         /// <summary>        /// Dequeues all of the items into the provided array        /// </summary>        /// <param name="items">where to put the items</param>        /// <param name="startingIndex">the starting index</param>        /// <param name="length">the maximum number of times to store</param>        /// <returns>the number of items dequeued</returns>        public int Dequeue(T[] items, int startingIndex, int length)        {            items.ValidateParameters(startingIndex, length);            int itemCount = 0;         TryAgain:             if (m_currentTail == null)            {                if (!m_blocks.TryDequeue(out m_currentTail))                {                    return 0;                }            }             if (m_currentTail.CanDequeue)            {                //Dequeue as many items as possible                itemCount += m_currentTail.Dequeue(items, ref startingIndex, ref length);                 //If no more items can fit in parameter: items, then exit                if (length == 0)                {                    m_dequeueCount += itemCount;                    return itemCount;                }            }             //If tail position of node is not at the end, all of the items in the Isolated Queue have been read.            //Note: because of a race condition, it may not still be empty,            //but cordinating that closely may cause drastically decrease performance.            if (!m_currentTail.DequeueMustMoveToNextNode)            {                m_dequeueCount += itemCount;                return itemCount;            }             //Don't reset the node on return since it is still            //possible for the enqueue thread to be using it.             //Note: If the enqueue thread pulls it off the queue            //immediately, this is ok since it will be coordinated at that point.            ReleaseNode(m_currentTail);             if (!m_blocks.TryDequeue(out m_currentTail))            {                m_dequeueCount += itemCount;                return itemCount;            }             goto TryAgain;        }          [MethodImpl(MethodImplOptions.NoInlining)]        bool TryDequeueSlower(out T item)        {            if (m_currentTail == null)            {                if (!m_blocks.TryDequeue(out m_currentTail))                {                    item = default(T);                    return false;                }            }            else if (m_currentTail.DequeueMustMoveToNextNode)            {                //Don't reset the node on return since it is still                //possible for the enqueue thread to be using it.                 //Note: If the enqueue thread pulls it off the queue                //immediately, this is ok since it will be coordinated at that point.                ReleaseNode(m_currentTail);                 if (!m_blocks.TryDequeue(out m_currentTail))                {                    item = default(T);                    return false;                }            }            if (m_currentTail.CanDequeue)            {                item = m_currentTail.Dequeue();                m_dequeueCount++;                return true;            }            item = default(T);            return false;        }          /// <summary>        /// Removes a node from the pool. If one does not exist, one is created.        /// </summary>        /// <returns></returns>        IsolatedNode GetNode()        {            IsolatedNode item;            if (m_pooledNodes.TryDequeue(out item))                return item;             return new IsolatedNode(m_unitCount);        }         /// <summary>        /// Addes an item back to the queue.        /// </summary>        /// <param name="resource"></param>        void ReleaseNode(IsolatedNode resource)        {            //if the queue has too many node items. just let the old one get garbage collected.            if (m_pooledNodes.Count < 1000)            {                m_pooledNodes.Enqueue(resource);            }        }     }}

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