using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; namespace BF { /// /// Thread safe (blocking) expanding queue with TryDequeue() and EnqueueFirst() /// [DebuggerDisplay("Count={Count} Capacity={Capacity}")] internal sealed class NetSafeQueue { private readonly ReaderWriterLockSlim m_lock = new ReaderWriterLockSlim(); // Example: // m_capacity = 8 // m_size = 6 // m_head = 4 // // [0] item // [1] item (tail = ((head + size - 1) % capacity) // [2] // [3] // [4] item (head) // [5] item // [6] item // [7] item // private T[] m_items; private int m_size; private int m_head; /// /// Gets the number of items in the queue /// public int Count { get { m_lock.EnterReadLock(); int count = m_size; m_lock.ExitReadLock(); return count; } } /// /// Gets the current capacity for the queue /// public int Capacity { get { m_lock.EnterReadLock(); int capacity = m_items.Length; m_lock.ExitReadLock(); return capacity; } } /// /// NetQueue constructor /// public NetSafeQueue(int initialCapacity) { m_items = new T[initialCapacity]; } /// /// Adds an item last/tail of the queue /// public void Enqueue(T item) { m_lock.EnterWriteLock(); try { if (m_size == m_items.Length) SetCapacity(m_items.Length + 8); int slot = (m_head + m_size) % m_items.Length; m_items[slot] = item; m_size++; } finally { m_lock.ExitWriteLock(); } } /// /// Adds an item last/tail of the queue /// public void Enqueue(IEnumerable items) { m_lock.EnterWriteLock(); try { foreach (var item in items) { if (m_size == m_items.Length) SetCapacity(m_items.Length + 8); // @TODO move this out of loop int slot = (m_head + m_size) % m_items.Length; m_items[slot] = item; m_size++; } } finally { m_lock.ExitWriteLock(); } } /// /// Places an item first, at the head of the queue /// public void EnqueueFirst(T item) { m_lock.EnterWriteLock(); try { if (m_size >= m_items.Length) SetCapacity(m_items.Length + 8); m_head--; if (m_head < 0) m_head = m_items.Length - 1; m_items[m_head] = item; m_size++; } finally { m_lock.ExitWriteLock(); } } // must be called from within a write locked m_lock! private void SetCapacity(int newCapacity) { if (m_size == 0) { if (m_size == 0) { m_items = new T[newCapacity]; m_head = 0; return; } } T[] newItems = new T[newCapacity]; if (m_head + m_size - 1 < m_items.Length) { Array.Copy(m_items, m_head, newItems, 0, m_size); } else { Array.Copy(m_items, m_head, newItems, 0, m_items.Length - m_head); Array.Copy(m_items, 0, newItems, m_items.Length - m_head, (m_size - (m_items.Length - m_head))); } m_items = newItems; m_head = 0; } /// /// Gets an item from the head of the queue, or returns default(T) if empty /// public bool TryDequeue(out T item) { if (m_size == 0) { item = default(T); return false; } m_lock.EnterWriteLock(); try { if (m_size == 0) { item = default(T); return false; } item = m_items[m_head]; m_items[m_head] = default(T); m_head = (m_head + 1) % m_items.Length; m_size--; return true; } catch { #if DEBUG throw; #else item = default(T); return false; #endif } finally { m_lock.ExitWriteLock(); } } /// /// Gets all items from the head of the queue, or returns number of items popped /// public int TryDrain(IList addTo) { if (m_size == 0) return 0; m_lock.EnterWriteLock(); try { int added = m_size; while (m_size > 0) { var item = m_items[m_head]; addTo.Add(item); m_items[m_head] = default(T); m_head = (m_head + 1) % m_items.Length; m_size--; } return added; } finally { m_lock.ExitWriteLock(); } } /// /// Returns default(T) if queue is empty /// public T TryPeek(int offset) { if (m_size == 0) return default(T); m_lock.EnterReadLock(); try { if (m_size == 0) return default(T); return m_items[(m_head + offset) % m_items.Length]; } finally { m_lock.ExitReadLock(); } } /// /// Determines whether an item is in the queue /// public bool Contains(T item) { m_lock.EnterReadLock(); try { int ptr = m_head; for (int i = 0; i < m_size; i++) { if (m_items[ptr] == null) { if (item == null) return true; } else { if (m_items[ptr].Equals(item)) return true; } ptr = (ptr + 1) % m_items.Length; } return false; } finally { m_lock.ExitReadLock(); } } /// /// Copies the queue items to a new array /// public T[] ToArray() { m_lock.EnterReadLock(); try { T[] retval = new T[m_size]; int ptr = m_head; for (int i = 0; i < m_size; i++) { retval[i] = m_items[ptr++]; if (ptr >= m_items.Length) ptr = 0; } return retval; } finally { m_lock.ExitReadLock(); } } /// /// Removes all objects from the queue /// public void Clear() { m_lock.EnterWriteLock(); try { for (int i = 0; i < m_items.Length; i++) m_items[i] = default(T); m_head = 0; m_size = 0; } finally { m_lock.ExitWriteLock(); } } } }