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