From 63d76d8cef3d1fd9ee834740152f493575c4fb1d Mon Sep 17 00:00:00 2001 From: Drew Tingen Date: Thu, 23 Sep 2021 17:33:15 -0400 Subject: [PATCH] feat: Adding IcdAutoResetEvent and IcdMaunalResetEvent --- .../IcdAutoResetEventTest.cs | 188 ++++++++++++++++++ .../IcdManualResetEventTest.cs | 183 +++++++++++++++++ ICD.Common.Utils/AbstractIcdResetEvent.cs | 106 ++++++++++ .../ICD.Common.Utils_SimplSharp.csproj | 3 + ICD.Common.Utils/IcdAutoResetEvent.cs | 39 ++++ ICD.Common.Utils/IcdManualResetEvent.cs | 40 ++++ 6 files changed, 559 insertions(+) create mode 100644 ICD.Common.Utils.Tests/IcdAutoResetEventTest.cs create mode 100644 ICD.Common.Utils.Tests/IcdManualResetEventTest.cs create mode 100644 ICD.Common.Utils/AbstractIcdResetEvent.cs create mode 100644 ICD.Common.Utils/IcdAutoResetEvent.cs create mode 100644 ICD.Common.Utils/IcdManualResetEvent.cs diff --git a/ICD.Common.Utils.Tests/IcdAutoResetEventTest.cs b/ICD.Common.Utils.Tests/IcdAutoResetEventTest.cs new file mode 100644 index 0000000..75b852b --- /dev/null +++ b/ICD.Common.Utils.Tests/IcdAutoResetEventTest.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ICD.Common.Utils.Tests +{ + [TestFixture] + class IcdAutoResetEventTest + { + [TestCase(true)] + [TestCase(false)] + public void InitialStateTest(bool initialState) + { + using (var waitHandleEvent = new IcdAutoResetEvent(initialState)) + { + Assert.AreEqual(initialState, waitHandleEvent.WaitOne(1), "Initial state incorrect"); + } + } + + [Test] + public void DefaultInitialStateTest() + { + using (var waitHandleEvent = new IcdAutoResetEvent()) + { + Assert.False(waitHandleEvent.WaitOne(1), "Initial state incorrect"); + } + } + + [TestCase(1)] + [TestCase(5)] + [TestCase(15)] + public void MultipleThreadSetTest(int count) + { + int releasedCount = 0; + + SafeCriticalSection countSection = new SafeCriticalSection(); + + + IcdAutoResetEvent waitHandleEvent = new IcdAutoResetEvent(false); + + for (int i = 0; i < count; i++) + ThreadingUtils.SafeInvoke(() => + { + waitHandleEvent.WaitOne(); + countSection.Execute(() => releasedCount++); + ; + }); + + Assert.AreEqual(0, countSection.Execute(() => releasedCount), "Threads released early"); + + ///Auto reset should only release one thread at a time + for (int i = 1; i <= count; i++) + { + waitHandleEvent.Set(); + + ThreadingUtils.Sleep(100); + + Assert.AreEqual(i, countSection.Execute(() => releasedCount), + "Incorrect number of threads released"); + } + + waitHandleEvent.Dispose(); + } + + [TestCase(1)] + [TestCase(5)] + [TestCase(15)] + public void MultipleThreadResetTest(int count) + { + int releasedCount = 0; + + SafeCriticalSection countSection = new SafeCriticalSection(); + + + IcdAutoResetEvent waitHandleEvent = new IcdAutoResetEvent(true); + + Assert.True(waitHandleEvent.WaitOne(1), "Initial State Wrong"); + + waitHandleEvent.Reset(); + + for (int i = 0; i < count; i++) + ThreadingUtils.SafeInvoke(() => + { + if (waitHandleEvent.WaitOne(100)) + countSection.Execute(() => releasedCount++); + }); + + ThreadingUtils.Sleep(2000); + + Assert.AreEqual(0, countSection.Execute(() => releasedCount), "Incorrect number of threads released"); + + waitHandleEvent.Set(); + + Assert.AreEqual(0, countSection.Execute(() => releasedCount), "Threads released after timeout"); + + waitHandleEvent.Dispose(); + } + + [TestCase(1)] + [TestCase(200)] + [TestCase(5000)] + public void WaitOneTest(int waitTime) + { + bool released = false; + + IcdAutoResetEvent waitHandleEvent = new IcdAutoResetEvent(false); + + ThreadingUtils.SafeInvoke(() => + { + waitHandleEvent.WaitOne(); + released = true; + }); + + ThreadingUtils.Sleep(waitTime); + + Assert.False(released, "Thread released when it shouldn't have"); + + waitHandleEvent.Set(); + + ThreadingUtils.Sleep(100); + + Assert.True(released, "Thread didn't release after set event"); + + waitHandleEvent.Dispose(); + } + + [TestCase(200)] + [TestCase(500)] + [TestCase(5000)] + public void WaitOneTimeoutTest(int waitTime) + { + bool released = false; + + IcdAutoResetEvent waitHandleEvent = new IcdAutoResetEvent(false); + + ThreadingUtils.SafeInvoke(() => + { + waitHandleEvent.WaitOne(waitTime * 2); + released = true; + }); + + ThreadingUtils.Sleep(waitTime); + + Assert.False(released, "Thread released when it shouldn't have"); + + waitHandleEvent.Set(); + + ThreadingUtils.Sleep(100); + + Assert.True(released, "Thread didn't release after set event"); + + waitHandleEvent.Dispose(); + } + + [TestCase(200)] + [TestCase(500)] + [TestCase(5000)] + public void WaitOneTimedOutTest(int waitTime) + { + bool released = false; + bool? returned = null; + + IcdAutoResetEvent waitHandleEvent = new IcdAutoResetEvent(false); + + ThreadingUtils.SafeInvoke(() => + { + returned = waitHandleEvent.WaitOne(waitTime); + released = true; + + }); + + ThreadingUtils.Sleep(100); + + Assert.True(returned == null, "WaitOne returned when it shouldn't have"); + Assert.False(released, "Thread released when it shouldn't have"); + + ThreadingUtils.Sleep(waitTime * 2); + + Assert.True(released, "Thread didn't release after timeout"); + Assert.True(returned.HasValue && returned.Value == false, "WaitOne timeout didn't return false"); + + waitHandleEvent.Set(); + + waitHandleEvent.Dispose(); + } + } +} diff --git a/ICD.Common.Utils.Tests/IcdManualResetEventTest.cs b/ICD.Common.Utils.Tests/IcdManualResetEventTest.cs new file mode 100644 index 0000000..1380512 --- /dev/null +++ b/ICD.Common.Utils.Tests/IcdManualResetEventTest.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ICD.Common.Utils.Tests +{ + [TestFixture] + class IcdManualResetEventTest + { + [TestCase(true)] + [TestCase(false)] + public void InitialStateTest(bool initialState) + { + using (var waitHandleEvent = new IcdManualResetEvent(initialState)) + { + Assert.AreEqual(initialState, waitHandleEvent.WaitOne(1), "Initial state incorrect"); + } + } + + [Test] + public void DefaultInitialStateTest() + { + using (var waitHandleEvent = new IcdManualResetEvent()) + { + Assert.False(waitHandleEvent.WaitOne(1), "Initial state incorrect"); + } + } + + [TestCase(1)] + [TestCase(5)] + [TestCase(15)] + public void MultipleThreadSetTest(int count) + { + int releasedCount = 0; + + SafeCriticalSection countSection = new SafeCriticalSection(); + + + IcdManualResetEvent waitHandleEvent = new IcdManualResetEvent(false); + + for (int i = 0; i < count; i++) + ThreadingUtils.SafeInvoke(() => + { + waitHandleEvent.WaitOne(); + countSection.Execute(() => releasedCount++); + ; + }); + + Assert.AreEqual(0, countSection.Execute(() => releasedCount), "Threads released early"); + + waitHandleEvent.Set(); + + ThreadingUtils.Sleep(500); + + Assert.AreEqual(count, countSection.Execute(() => releasedCount), "Incorrect number of threads released"); + + waitHandleEvent.Dispose(); + } + + [TestCase(1)] + [TestCase(5)] + [TestCase(15)] + public void MultipleThreadResetTest(int count) + { + int releasedCount = 0; + + SafeCriticalSection countSection = new SafeCriticalSection(); + + + IcdManualResetEvent waitHandleEvent = new IcdManualResetEvent(true); + + Assert.True(waitHandleEvent.WaitOne(1), "Initial State Wrong"); + + waitHandleEvent.Reset(); + + for (int i = 0; i < count; i++) + ThreadingUtils.SafeInvoke(() => + { + if (waitHandleEvent.WaitOne(100)) + countSection.Execute(() => releasedCount++); + }); + + ThreadingUtils.Sleep(2000); + + Assert.AreEqual(0, countSection.Execute(() => releasedCount), "Incorrect number of threads released"); + + waitHandleEvent.Set(); + + Assert.AreEqual(0, countSection.Execute(() => releasedCount), "Threads released after timeout"); + + waitHandleEvent.Dispose(); + } + + [TestCase(1)] + [TestCase(200)] + [TestCase(5000)] + public void WaitOneTest(int waitTime) + { + bool released = false; + + IcdManualResetEvent waitHandleEvent = new IcdManualResetEvent(false); + + ThreadingUtils.SafeInvoke(() => + { + waitHandleEvent.WaitOne(); + released = true; + }); + + ThreadingUtils.Sleep(waitTime); + + Assert.False(released, "Thread released when it shouldn't have"); + + waitHandleEvent.Set(); + + ThreadingUtils.Sleep(100); + + Assert.True(released, "Thread didn't release after set event"); + + waitHandleEvent.Dispose(); + } + + [TestCase(200)] + [TestCase(500)] + [TestCase(5000)] + public void WaitOneTimeoutTest(int waitTime) + { + bool released = false; + + IcdManualResetEvent waitHandleEvent = new IcdManualResetEvent(false); + + ThreadingUtils.SafeInvoke(() => + { + waitHandleEvent.WaitOne(waitTime * 2); + released = true; + }); + + ThreadingUtils.Sleep(waitTime); + + Assert.False(released, "Thread released when it shouldn't have"); + + waitHandleEvent.Set(); + + ThreadingUtils.Sleep(100); + + Assert.True(released, "Thread didn't release after set event"); + + waitHandleEvent.Dispose(); + } + + [TestCase(200)] + [TestCase(500)] + [TestCase(5000)] + public void WaitOneTimedOutTest(int waitTime) + { + bool released = false; + bool? returned = null; + + IcdManualResetEvent waitHandleEvent = new IcdManualResetEvent(false); + + ThreadingUtils.SafeInvoke(() => + { + returned = waitHandleEvent.WaitOne(waitTime); + released = true; + + }); + + ThreadingUtils.Sleep(100); + + Assert.True(returned == null, "WaitOne returned when it shouldn't have"); + Assert.False(released, "Thread released when it shouldn't have"); + + ThreadingUtils.Sleep(waitTime * 2); + + Assert.True(released, "Thread didn't release after timeout"); + Assert.True(returned.HasValue && returned.Value == false, "WaitOne timeout didn't return false"); + + waitHandleEvent.Set(); + + waitHandleEvent.Dispose(); + } + } +} diff --git a/ICD.Common.Utils/AbstractIcdResetEvent.cs b/ICD.Common.Utils/AbstractIcdResetEvent.cs new file mode 100644 index 0000000..4d9fca4 --- /dev/null +++ b/ICD.Common.Utils/AbstractIcdResetEvent.cs @@ -0,0 +1,106 @@ +using System; +using ICD.Common.Properties; +#if SIMPLSHARP +using Crestron.SimplSharp; +#else +using System.Threading; +#endif + +namespace ICD.Common.Utils +{ + public abstract class AbstractIcdResetEvent : IDisposable + { + +#if SIMPLSHARP + private readonly CEvent m_Event; +#else + private readonly EventWaitHandle m_Event; +#endif + +#if SIMPLSHARP + /// + /// Initializes a new instance of the IcdManualResetEvent class with the CEvent + /// + [PublicAPI] + protected AbstractIcdResetEvent(CEvent eventHandle) + { + m_Event = eventHandle; + } +#else + /// + /// Initializes a new instance of the IcdManualResetEvent class with the CEvent + /// + [PublicAPI] + protected AbstractIcdResetEvent(EventWaitHandle eventHandle) + { + m_Event = eventHandle; + } + +#endif + + /// + /// Sets the state of the event to signaled, allowing one or more waiting threads to proceed. + /// + /// true if the operation succeeds; otherwise, false. + [PublicAPI] + public bool Set() + { + return m_Event.Set(); + } + + /// + /// Sets the state of the event to nonsignaled, causing threads to block. + /// + /// true if the operation succeeds; otherwise, false. + [PublicAPI] + public bool Reset() + { + return m_Event.Reset(); + } + + /// + /// Function to wait for the event to be signaled. This will block indefinitely until the event is signaled. + /// + /// True if the current instance receives a signal otherwise false. + [PublicAPI] + public bool WaitOne() + { +#if SIMPLSHARP + return m_Event.Wait(); +#else + return m_Event.WaitOne(); +#endif + } + + /// + /// Function to wait for the event to be signaled. + /// + /// Timeout in milliseconds or Timeout.Infinite to wait indefinitely. + /// True if the current instance receives a signal otherwise false. + public bool WaitOne(int timeout) + { +#if SIMPLSHARP + return m_Event.Wait(timeout); +#else + return m_Event.WaitOne(timeout); +#endif + } + + /// + /// Clean up of resources. + /// + public void Dispose() + { + m_Event.Dispose(); + } + + /// + /// Close the event to release all resources used by this instance. + /// + [PublicAPI] + public void Close() + { + m_Event.Close(); + } + } +} \ 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 9124253..75c17bb 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -121,6 +121,9 @@ + + + diff --git a/ICD.Common.Utils/IcdAutoResetEvent.cs b/ICD.Common.Utils/IcdAutoResetEvent.cs new file mode 100644 index 0000000..4eac0c5 --- /dev/null +++ b/ICD.Common.Utils/IcdAutoResetEvent.cs @@ -0,0 +1,39 @@ +using ICD.Common.Properties; +#if SIMPLSHARP +using Crestron.SimplSharp; +# else +using System.Threading; +#endif + +namespace ICD.Common.Utils +{ + /// + /// Notifies one or more waiting threads that an event has occurred. Every thread that passes WaitOne causes the event to be reset + /// + [PublicAPI] + public sealed class IcdAutoResetEvent : AbstractIcdResetEvent + { + + /// + /// Initializes a new instance of the IcdAutoResetEvent class with the initial state to nonsignaled. + /// + [PublicAPI] + public IcdAutoResetEvent() : this(false) + { + } + + /// + /// Initializes a new instance of the IcdManualResetEvent class with a Boolean value indicating whether to set the initial state to signaled. + /// + /// true to set the initial state signaled; false to set the initial state to nonsignaled. + [PublicAPI] + public IcdAutoResetEvent(bool initialState) : +#if SIMPLSHARP + base(new CEvent(true, initialState)) +#else + base(new AutoResetEvent(initialState)) +#endif + { + } + } +} \ No newline at end of file diff --git a/ICD.Common.Utils/IcdManualResetEvent.cs b/ICD.Common.Utils/IcdManualResetEvent.cs new file mode 100644 index 0000000..0618555 --- /dev/null +++ b/ICD.Common.Utils/IcdManualResetEvent.cs @@ -0,0 +1,40 @@ +using ICD.Common.Properties; +#if SIMPLSHARP +using Crestron.SimplSharp; +#else +using System.Threading; +#endif + +namespace ICD.Common.Utils +{ + /// + /// Notifies one or more waiting threads that an event has occurred. + /// + [PublicAPI] + public sealed class IcdManualResetEvent : AbstractIcdResetEvent + { + + /// + /// Initializes a new instance of the IcdManualResetEvent class with the initial state to nonsignaled. + /// + [PublicAPI] + public IcdManualResetEvent() : this(false) + { + } + + /// + /// Initializes a new instance of the IcdManualResetEvent class with a Boolean value indicating whether to set the initial state to signaled. + /// + /// true to set the initial state signaled; false to set the initial state to nonsignaled. + [PublicAPI] + public IcdManualResetEvent(bool initialState) : + +#if SIMPLSHARP + base(new CEvent(false, initialState)) +#else + base(new ManualResetEvent(initialState)) +#endif + { + } + } +} \ No newline at end of file