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