diff --git a/ThreadTester/Events/AutoResetEventEx.cs b/ThreadTester/Events/AutoResetEventEx.cs
new file mode 100644
index 0000000..e946747
--- /dev/null
+++ b/ThreadTester/Events/AutoResetEventEx.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Security.Permissions;
+using System.Text;
+using System.Threading;
+
+namespace Osherove.ThreadTester.Events
+{
+ [ComVisible(true), HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
+ public class AutoResetEventEx : EventWaitHandleEx
+ {
+ // Methods
+ public AutoResetEventEx(bool initialState)
+ : base(initialState, EventResetMode.AutoReset)
+ {
+ }
+ }
+}
diff --git a/ThreadTester/Events/EventWaitHandleEx.cs b/ThreadTester/Events/EventWaitHandleEx.cs
new file mode 100644
index 0000000..92086b6
--- /dev/null
+++ b/ThreadTester/Events/EventWaitHandleEx.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Security.AccessControl;
+using System.Security.Permissions;
+using System.Threading;
+
+namespace Osherove.ThreadTester.Events
+{
+
+ [ComVisible(true), HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
+ public class EventWaitHandleEx : EventWaitHandle
+ {
+ public delegate void WaitDelegate(object sender, WaitEventArgs args);
+ public event WaitDelegate BeforeWaitCalled = delegate { };
+ public event EventHandler Closed = delegate { };
+
+ public EventWaitHandleEx(bool initialState, EventResetMode mode)
+ : base(initialState, mode)
+ {
+ }
+
+
+ public override bool WaitOne(TimeSpan timeout, bool exitContext)
+ {
+ bool cancel = false;
+ OnWait((int?) timeout.TotalMilliseconds, exitContext, ref cancel);
+ if (cancel)
+ {
+ return false;
+ }
+
+ return base.WaitOne(timeout, exitContext);
+ }
+
+ public override bool WaitOne(int millisecondsTimeout, bool exitContext)
+ {
+ bool cancel = false;
+ OnWait(millisecondsTimeout, exitContext, ref cancel);
+ if (cancel && allowWaitCanceling)
+ {
+ return false;
+ }
+ return base.WaitOne(millisecondsTimeout, exitContext);
+ }
+
+
+ public override bool WaitOne()
+ {
+ bool cancel = false;
+ OnWait(null, null,ref cancel);
+ if (cancel)
+ {
+ return false;
+ }
+ return base.WaitOne();
+ }
+
+
+ private bool allowWaitCanceling;
+
+ ///
+ /// Setting this to true will trigger the BeforeWaitCalled event
+ /// in a synchronized fasion. when false, the event is thrown in asyc (new thread).
+ ///
+ public bool AllowWaitCanceling
+ {
+ get { return allowWaitCanceling; }
+ set { allowWaitCanceling = value; }
+ }
+
+ private void TriggerWaitCalledNewThread(int? timeout, bool? exitContext, ref bool cancel)
+ {
+
+ WaitEventArgs args = new WaitEventArgs(timeout, exitContext);
+ Thread t = new Thread(new ThreadStart(delegate
+ {
+ SafeTrigger(BeforeWaitCalled, this, args);
+ if (args.CancelWait)
+ {
+ ArgumentException exception = new ArgumentException("You can't cancel a call to WaitOne() without setting AllowWaitCanceling to true");
+ ThreadManager.exceptions.Add(exception);
+ throw exception;
+ }
+ }));
+ t.Start();
+ }
+ private static void SafeTrigger(Delegate del, params object[] args)
+ {
+// // return;
+// del.DynamicInvoke(args);
+// return;
+
+ foreach (Delegate callback in del.GetInvocationList())
+ {
+ try
+ {
+ callback.DynamicInvoke(args);
+ }
+ catch (Exception e)
+ {
+ // Console.WriteLine(e.ToString());
+ }
+ }
+ }
+ private void TriggerWaitCalledSameThread(int? timeout, bool? exitContext, ref bool cancel)
+ {
+ WaitEventArgs args = new WaitEventArgs(timeout, exitContext);
+ SafeTrigger(BeforeWaitCalled, this, args);
+ if (args.CancelWait)
+ {
+ cancel = true;
+ }
+ }
+ private int waiters;
+
+ public int Waiters
+ {
+ get { return waiters; }
+ set { waiters = value; }
+ }
+
+ private void OnWait(int? timeout, bool? exitContext, ref bool cancel)
+ {
+
+ waiters++;
+ if (allowWaitCanceling)
+ {
+ TriggerWaitCalledSameThread(timeout, exitContext, ref cancel);
+ if(cancel)
+ {
+ waiters--;
+ }
+ }
+ else
+ {
+ TriggerWaitCalledNewThread(timeout, exitContext, ref cancel);
+ }
+
+ }
+
+
+
+ }
+
+
+
+
+}
\ No newline at end of file
diff --git a/ThreadTester/Events/ManualResetEventEx.cs b/ThreadTester/Events/ManualResetEventEx.cs
new file mode 100644
index 0000000..ab7f7e7
--- /dev/null
+++ b/ThreadTester/Events/ManualResetEventEx.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Security.Permissions;
+using System.Text;
+using System.Threading;
+
+namespace Osherove.ThreadTester.Events
+{
+//
+ public class ManualResetEventEx : EventWaitHandleEx
+ {
+ public ManualResetEventEx(bool initialState)
+ : base(initialState, EventResetMode.ManualReset)
+ {
+ }
+ }
+}
diff --git a/ThreadTester/Events/UnhandledException.cs b/ThreadTester/Events/UnhandledException.cs
new file mode 100644
index 0000000..b893e1c
--- /dev/null
+++ b/ThreadTester/Events/UnhandledException.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Osherove.ThreadTester.Events
+{
+ public class UnhandledException:Exception
+ {
+ private object sender;
+ private UnhandledExceptionEventArgs args;
+
+ public UnhandledException(object sender, UnhandledExceptionEventArgs args)
+ {
+ this.sender = sender;
+ this.args = args;
+ }
+
+ public object Sender
+ {
+ get { return sender; }
+ set { sender = value; }
+ }
+
+ public UnhandledExceptionEventArgs Args
+ {
+ get { return args; }
+ set { args = value; }
+ }
+ }
+}
diff --git a/ThreadTester/Events/WaitEventArgs.cs b/ThreadTester/Events/WaitEventArgs.cs
new file mode 100644
index 0000000..5c11460
--- /dev/null
+++ b/ThreadTester/Events/WaitEventArgs.cs
@@ -0,0 +1,47 @@
+using System;
+
+namespace Osherove.ThreadTester.Events
+{
+ public class WaitEventArgs:EventArgs
+ {
+ private bool cancelwait;
+
+ public bool CancelWait
+ {
+ get { return cancelwait; }
+ set { cancelwait = value; }
+ }
+
+ private int? timeout;
+
+ public int? TimeOut
+ {
+ get { return timeout; }
+ set { timeout = value; }
+ }
+
+ private bool? exitContext;
+
+ public bool? ExitContext
+ {
+ get { return exitContext; }
+ set { exitContext = value; }
+ }
+
+ public WaitEventArgs()
+ {
+ }
+
+
+ public WaitEventArgs(int? timeout)
+ {
+ this.timeout = timeout;
+ }
+
+ public WaitEventArgs(int? timeout, bool? exitContext)
+ {
+ this.timeout = timeout;
+ this.exitContext = exitContext;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ThreadTester/History.txt b/ThreadTester/History.txt
new file mode 100644
index 0000000..0bade1f
--- /dev/null
+++ b/ThreadTester/History.txt
@@ -0,0 +1,5 @@
+
+1.00.1
+----------
+- Added ResetAbort() to threads to prevent ugly exceptions
+- Added .StopWhenTrue() ability
\ No newline at end of file
diff --git a/ThreadTester/Tests/EventWaitHandleExTests.cs b/ThreadTester/Tests/EventWaitHandleExTests.cs
new file mode 100644
index 0000000..aa28c90
--- /dev/null
+++ b/ThreadTester/Tests/EventWaitHandleExTests.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using NUnit.Framework;
+using Osherove.ThreadTester.Events;
+
+namespace Osherove.ThreadTester.Tests
+{
+ [TestFixture]
+ public class EventWaitHandleExTests
+ {
+ [Test]
+// [ExpectedException(typeof(ArgumentException), "You can't cancel a call to WaitOne() without setting AllowWaitCanceling to true")]
+ public void WaitOne_CancelSetToTrueOnEvent_CanCancelWait()
+ {
+ AutoResetEventEx e = new AutoResetEventEx(false);
+ e.BeforeWaitCalled+=delegate(object sender, WaitEventArgs args)
+ {
+ args.CancelWait = true;
+ };
+ e.WaitOne(100, true);
+ Assert.AreEqual(1,ThreadManager.exceptions.Count);
+
+ }
+
+ [Test]
+ public void BeforeWaitCalled_Triggered()
+ {
+ AutoResetEventEx e = new AutoResetEventEx(false);
+ e.BeforeWaitCalled += delegate(object sender, WaitEventArgs args)
+ {
+ Assert.AreEqual(100,args.TimeOut);
+ Assert.AreEqual(true,args.ExitContext);
+ Console.WriteLine("wait({0},{1})", args.TimeOut, args.ExitContext);
+ };
+ bool result = e.WaitOne(100, true);
+ Assert.AreEqual(1, e.Waiters);
+ Console.WriteLine("done");
+ }
+
+ [Test]
+ public void BeforeWaitCalled_TriggeredAndCancenled()
+ {
+ AutoResetEventEx e = new AutoResetEventEx(false);
+ e.AllowWaitCanceling = true;
+ e.BeforeWaitCalled += delegate(object sender, WaitEventArgs args)
+ {
+ args.CancelWait = true;
+ };
+ bool result = e.WaitOne(1000, true);
+ Assert.AreEqual(0,e.Waiters);
+ Console.WriteLine("done");
+ }
+ }
+}
diff --git a/ThreadTester/ThreadManager.cs b/ThreadTester/ThreadManager.cs
new file mode 100644
index 0000000..e8f14f7
--- /dev/null
+++ b/ThreadTester/ThreadManager.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Timers;
+using NUnit.Framework;
+using Osherove.ThreadTester.Strategies;
+using Osherove.ThreadTester.Strategies;
+using Osherove.ThreadTester.Tests;
+using Osherove.ThreadTester.Strategies;
+using Timer=System.Timers.Timer;
+
+namespace Osherove.ThreadTester
+{
+ public delegate void Func();
+ public delegate bool CheckDelegate();
+ public delegate void ThreadFinishedDelegate(ThreadAction threadAction);
+
+ public class ThreadManager
+ {
+ private IThreadRunStrategy runner = new AllThreadsShouldFinishStrategy();
+
+ private readonly List threadActions = new List();
+ private long lastRunTime;
+ readonly Stopwatch stopwatch = new Stopwatch();
+ private long timeOut;
+ private ThreadRunBehavior runBehavior=ThreadRunBehavior.RunUntilAllThreadsFinish;
+
+ void SignalThreadIsFinished(ThreadAction threadAction)
+ {
+ runner.OnThreadFinished(threadAction);
+
+ }
+
+ internal static readonly List exceptions = new List();
+
+ public static List Exceptions
+ {
+ get { return exceptions; }
+ }
+
+ public void AddThreadAction(Func ActionCallback)
+ {
+ ThreadAction action = new ThreadAction(ActionCallback);
+ action.SignalFinishedCallback = SignalThreadIsFinished;
+ threadActions.Add(action);
+ }
+
+ public List ThreadActions
+ {
+ get { return threadActions; }
+ }
+
+
+ public long LastRunTime
+ {
+ get { return lastRunTime; }
+ }
+
+ public long TimeOut
+ {
+ get { return timeOut; }
+ }
+
+ public void StartAllThreads(int runningTimeout)
+ {
+ runner = CreateStrategy(RunBehavior);
+ timeOut = runningTimeout;
+ stopwatch.Reset();
+ stopwatch.Start();
+ checkTimer.Start();
+
+ runner.StartAll(runningTimeout, threadActions);
+
+ checkTimer.Stop();
+ stopwatch.Stop();
+ lastRunTime = stopwatch.ElapsedMilliseconds;
+ }
+
+
+ public ThreadRunBehavior RunBehavior
+ {
+ get { return runBehavior; }
+ set { runBehavior = value; }
+ }
+
+ protected IThreadRunStrategy CreateStrategy(ThreadRunBehavior val)
+ {
+ Dictionary runStrategies = new Dictionary();
+ runStrategies.Add(ThreadRunBehavior.RunForSpecificTime, new RunForSpecificTimeStrategy());
+ runStrategies.Add(ThreadRunBehavior.RunUntilAllThreadsFinish, new AllThreadsShouldFinishStrategy());
+
+ return runStrategies[val];
+ }
+
+ private Timer checkTimer = new Timer();
+ public void StopWhenTrue(CheckDelegate checkCallback,double interval)
+ {
+ checkTimer = new Timer(interval);
+ checkTimer.Elapsed+=delegate {
+ if(checkCallback())
+ {
+ checkTimer.Stop();
+ runner.StopAll();
+ }
+ };
+
+ }
+ }
+}
\ No newline at end of file