diff --git a/ICD.Common.Utils.Tests/Collections/AsyncEventQueueTest.cs b/ICD.Common.Utils.Tests/Collections/AsyncEventQueueTest.cs new file mode 100644 index 0000000..ad8d989 --- /dev/null +++ b/ICD.Common.Utils.Tests/Collections/AsyncEventQueueTest.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using ICD.Common.Utils.Collections; +using ICD.Common.Utils.EventArguments; +using NUnit.Framework; + +namespace ICD.Common.Utils.Tests.Collections +{ + [TestFixture] + public sealed class AsyncEventQueueTest + { + [Test] + public void ItemDequeuedFeedbackTest() + { + Assert.Inconclusive(); + } + + [Test] + public void CountTest() + { + using (AsyncEventQueue queue = new AsyncEventQueue()) + { + queue.OnItemDequeued += + (sender, args) => + { + ThreadingUtils.Sleep(200); + }; + + Assert.AreEqual(0, queue.Count); + + for (int index = 0; index < 5; index++) + queue.Enqueue(index); + + Assert.AreEqual(5, queue.Count); + } + } + + [Test] + public void EnqueueTest() + { + using (AsyncEventQueue queue = new AsyncEventQueue()) + { + List> eventArgs = new List>(); + queue.OnItemDequeued += + (sender, args) => + { + eventArgs.Add(args); + }; + + Assert.AreEqual(0, eventArgs.Count); + + for (int index = 0; index < 5; index++) + queue.Enqueue(index); + + ThreadingUtils.Sleep(500); + + Assert.AreEqual(5, eventArgs.Count); + + Assert.AreEqual(0, eventArgs[0].Data); + Assert.AreEqual(1, eventArgs[1].Data); + Assert.AreEqual(2, eventArgs[2].Data); + Assert.AreEqual(3, eventArgs[3].Data); + Assert.AreEqual(4, eventArgs[4].Data); + } + } + + [Test] + public void ClearTest() + { + using (AsyncEventQueue queue = new AsyncEventQueue()) + { + queue.OnItemDequeued += + (sender, args) => + { + ThreadingUtils.Sleep(200); + }; + + Assert.AreEqual(0, queue.Count); + + for (int index = 0; index < 5; index++) + queue.Enqueue(index); + + Assert.AreEqual(5, queue.Count); + + queue.Clear(); + + Assert.AreEqual(0, queue.Count); + } + } + } +} diff --git a/ICD.Common.Utils/Collections/AsyncEventQueue.cs b/ICD.Common.Utils/Collections/AsyncEventQueue.cs new file mode 100644 index 0000000..7997e99 --- /dev/null +++ b/ICD.Common.Utils/Collections/AsyncEventQueue.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using ICD.Common.Utils.EventArguments; +using ICD.Common.Utils.Extensions; + +namespace ICD.Common.Utils.Collections +{ + /// + /// Provides a buffer for storing items to be raised in order in a new thread. + /// + /// + public sealed class AsyncEventQueue : IEnumerable, ICollection, IDisposable + { + /// + /// Raised to handle to the next item in the queue. + /// + public event EventHandler> OnItemDequeued; + + private readonly Queue m_Queue; + + private readonly SafeCriticalSection m_QueueSection; + private readonly SafeCriticalSection m_ProcessSection; + + #region Properties + + public int Count { get { return m_QueueSection.Execute(() => m_Queue.Count); } } + + bool ICollection.IsSynchronized { get { return true; } } + + object ICollection.SyncRoot { get { return this; } } + + #endregion + + /// + /// Constructor. + /// + public AsyncEventQueue() + { + m_Queue = new Queue(); + + m_QueueSection = new SafeCriticalSection(); + m_ProcessSection = new SafeCriticalSection(); + } + + #region Methods + + /// + /// Release resources. + /// + public void Dispose() + { + OnItemDequeued = null; + + Clear(); + } + + /// + /// Enqueues the given item and begins processing the queue. + /// + /// + public void Enqueue(T item) + { + m_QueueSection.Execute(() => m_Queue.Enqueue(item)); + + ThreadingUtils.SafeInvoke(ProcessQueue); + } + + /// + /// Clears the queued items. + /// + public void Clear() + { + m_QueueSection.Execute(() => m_Queue.Clear()); + } + + #endregion + + #region Private Methods + + /// + /// Dequeues and raises each item in sequence. + /// + private void ProcessQueue() + { + if (!m_ProcessSection.TryEnter()) + return; + + try + { + T item; + while (m_Queue.Dequeue(out item)) + OnItemDequeued.Raise(this, new GenericEventArgs(item)); + } + finally + { + m_ProcessSection.Leave(); + } + } + + #endregion + + #region IEnumerable/ICollection + + public IEnumerator GetEnumerator() + { + return m_QueueSection.Execute(() => m_Queue.ToList(m_Queue.Count).GetEnumerator()); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + void ICollection.CopyTo(Array array, int index) + { + m_QueueSection.Enter(); + + try + { + foreach (T item in this) + { + array.SetValue(item, index); + index++; + } + } + finally + { + m_QueueSection.Leave(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj index 1e6c200..697ba65 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -75,6 +75,7 @@ +