diff --git a/Source/Scheduling/AbortedException.Test.cs b/Source/Scheduling/AbortedException.Test.cs deleted file mode 100644 index 0881b15..0000000 --- a/Source/Scheduling/AbortedException.Test.cs +++ /dev/null @@ -1,83 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; - -using NUnit.Framework; - -namespace Nuclex.Support.Scheduling { - - /// Unit Test for the AbortedException class - [TestFixture] - public class AbortedExceptionTest { - - /// - /// Verifies that the exception's default constructor is working - /// - [Test] - public void TestDefaultConstructor() { - AbortedException testException = new AbortedException(); - - string testExceptionString = testException.ToString(); - Assert.IsNotNull(testExceptionString); - } - - /// - /// Checks whether the exception correctly stores its inner exception - /// - [Test] - public void TestInnerException() { - Exception inner = new Exception("This is a test"); - AbortedException testException = new AbortedException( - "Hello World", inner - ); - - Assert.AreSame(inner, testException.InnerException); - } - - /// - /// Test whether the exception can be serialized - /// - [Test] - public void TestSerialization() { - BinaryFormatter formatter = new BinaryFormatter(); - - using(MemoryStream memory = new MemoryStream()) { - AbortedException exception1 = new AbortedException("Hello World"); - - formatter.Serialize(memory, exception1); - memory.Position = 0; - object exception2 = formatter.Deserialize(memory); - - Assert.IsInstanceOf(exception2); - Assert.AreEqual(exception1.Message, ((AbortedException)exception2).Message); - } - } - - } - -} // namespace Nuclex.Support.Scheduling - -#endif // UNITTEST diff --git a/Source/Scheduling/AbortedException.cs b/Source/Scheduling/AbortedException.cs deleted file mode 100644 index 7e5f6ac..0000000 --- a/Source/Scheduling/AbortedException.cs +++ /dev/null @@ -1,63 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Scheduling { - - /// Indicates that an operation has been forcefully aborted - /// - /// This exception is the typical result of using AsyncAbort() on a running - /// background process. - /// -#if !NO_SERIALIZATION - [Serializable] -#endif - public class AbortedException : Exception { - - /// Initializes the exception - public AbortedException() { } - - /// Initializes the exception with an error message - /// Error message describing the cause of the exception - public AbortedException(string message) : base(message) { } - - /// Initializes the exception as a followup exception - /// Error message describing the cause of the exception - /// Preceding exception that has caused this exception - public AbortedException(string message, Exception inner) : base(message, inner) { } - -#if !NO_SERIALIZATION - - /// Initializes the exception from its serialized state - /// Contains the serialized fields of the exception - /// Additional environmental informations - protected AbortedException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context - ) : - base(info, context) { } - -#endif // !NO_SERIALIZATION - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/GenericTimeSource.Test.cs b/Source/Scheduling/GenericTimeSource.Test.cs deleted file mode 100644 index 97ceb23..0000000 --- a/Source/Scheduling/GenericTimeSource.Test.cs +++ /dev/null @@ -1,97 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.Threading; - -using NUnit.Framework; - -namespace Nuclex.Support.Scheduling { - - /// Unit Test for the generic scheduler time source - [TestFixture] - public class GenericTimeSourceTest { - - /// - /// Verifies that the time source's default constructor is working - /// - [Test] - public void TestDefaultConstructor() { - GenericTimeSource timeSource = new GenericTimeSource(); - } - - /// - /// Verifies that the time source can provide the current UTC time - /// - [Test] - public void TestCurrentUtcTime() { - GenericTimeSource timeSource = new GenericTimeSource(); - - Assert.That( - timeSource.CurrentUtcTime, Is.EqualTo(DateTime.UtcNow).Within(10).Seconds - ); - } - - /// - /// Verifies that the default time source's tick property is working if - /// the Stopwatch class is used to measure time - /// - [Test] - public void TestTicksWithStopwatch() { - GenericTimeSource timeSource = new GenericTimeSource(true); - long ticks1 = timeSource.Ticks; - long ticks2 = timeSource.Ticks; - - Assert.That(ticks2, Is.GreaterThanOrEqualTo(ticks1)); - } - - /// - /// Verifies that the default time source's tick property is working if - /// Environment.TickCount is used to measure time - /// - [Test] - public void TestTicksWithTickCount() { - GenericTimeSource timeSource = new GenericTimeSource(false); - long ticks1 = timeSource.Ticks; - long ticks2 = timeSource.Ticks; - - Assert.That(ticks2, Is.GreaterThanOrEqualTo(ticks1)); - } - - /// - /// Verifies that the default time source's WaitOne() method works correctly - /// - [Test] - public void TestWaitOne() { - GenericTimeSource timeSource = new GenericTimeSource(); - AutoResetEvent waitEvent = new AutoResetEvent(true); - - Assert.IsTrue(timeSource.WaitOne(waitEvent, TimeSpan.FromMilliseconds(1).Ticks)); - Assert.IsFalse(timeSource.WaitOne(waitEvent, TimeSpan.FromMilliseconds(1).Ticks)); - } - - } - -} // namespace Nuclex.Support.Scheduling - -#endif // UNITTEST diff --git a/Source/Scheduling/GenericTimeSource.cs b/Source/Scheduling/GenericTimeSource.cs deleted file mode 100644 index cc54cfe..0000000 --- a/Source/Scheduling/GenericTimeSource.cs +++ /dev/null @@ -1,189 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; - -namespace Nuclex.Support.Scheduling { - - /// - /// Generic time source implementation using the Stopwatch or Environment.TickCount - /// - public class GenericTimeSource : ITimeSource { - - /// Number of ticks (100 ns intervals) in a millisecond - private const long TicksPerMillisecond = 10000; - - /// Tolerance for the detection of a date/time adjustment - /// - /// If the current system date/time jumps by more than this tolerance into any - /// direction, the default time source will trigger the DateTimeAdjusted event. - /// - private const long TimeAdjustmentToleranceTicks = 75 * TicksPerMillisecond; - - /// Called when the system date/time are adjusted - /// - /// An adjustment is a change out of the ordinary, eg. when a time synchronization - /// alters the current system time, when daylight saving time takes effect or - /// when the user manually adjusts the system date/time. - /// - public event EventHandler DateTimeAdjusted; - - /// Initializes the static fields of the default time source - static GenericTimeSource() { - tickFrequency = 10000000.0; - tickFrequency /= (double)Stopwatch.Frequency; - } - - /// Initializes the default time source - public GenericTimeSource() : this(Stopwatch.IsHighResolution) { } - - /// Initializes the default time source - /// - /// Whether to use the Stopwatch class for measuring time - /// - /// - /// - /// Normally it's a good idea to use the default constructor. If the Stopwatch - /// is unable to use the high-resolution timer, it will fall back to - /// DateTime.Now (as stated on MSDN). This is bad because then the tick count - /// will jump whenever the system time changes (eg. when the system synchronizes - /// its time with a time server). - /// - /// - /// Your can safely use this constructor if you always set its arugment to 'false', - /// but then your won't profit from the high-resolution timer if one is available. - /// - /// - public GenericTimeSource(bool useStopwatch) { - this.useStopwatch = useStopwatch; - - // Update the lastCheckedTime and lastCheckedTicks fields - checkForTimeAdjustment(); - } - - /// Waits for an AutoResetEvent to become signalled - /// WaitHandle the method will wait for - /// Number of ticks to wait - /// - /// True if the WaitHandle was signalled, false if the timeout was reached - /// - public virtual bool WaitOne(AutoResetEvent waitHandle, long ticks) { - - // Force a timeout at least each second so the caller can check the system time - // since we're not able to provide the DateTimeAdjusted notification - int milliseconds = (int)(ticks / TicksPerMillisecond); -#if WINDOWS - bool signalled = waitHandle.WaitOne(Math.Min(1000, milliseconds), false); -#else - bool signalled = waitHandle.WaitOne(Math.Min(1000, milliseconds)); -#endif - // See whether the system date/time have been adjusted while we were asleep. - checkForTimeAdjustment(); - - // Now tell the caller whether his event was signalled - return signalled; - - } - - /// Current system time in UTC format - public DateTime CurrentUtcTime { - get { return DateTime.UtcNow; } - } - - /// How long the time source has been running - /// - /// There is no guarantee this value starts at zero (or anywhere near it) when - /// the time source is created. The only requirement for this value is that it - /// keeps increasing with the passing of time and that it stays unaffected - /// (eg. doesn't skip or jump back) when the system date/time are changed. - /// - public long Ticks { - get { - - // The docs say if Stopwatch.IsHighResolution is false, it will return - // DateTime.Now (actually DateTime.UtcNow). This means that the Stopwatch is - // prone to skips and jumps during DST crossings and NTP synchronizations, - // so we cannot use it in that case. - if(this.useStopwatch) { - double timestamp = (double)Stopwatch.GetTimestamp(); - return (long)(timestamp * tickFrequency); - } - - // Fallback: Use Environment.TickCount instead. Not as accurate, but at least - // it will not jump around when the date or time are adjusted. - return Environment.TickCount * TicksPerMillisecond; - - } - } - - /// Called when the system time is changed - /// Not used - /// Not used - protected virtual void OnDateTimeAdjusted(object sender, EventArgs arguments) { - EventHandler copy = DateTimeAdjusted; - if(copy != null) { - copy(sender, arguments); - } - } - - /// - /// Checks whether the system/date time have been adjusted since the last call - /// - private void checkForTimeAdjustment() { - - // Grab the current date/time and timer ticks in one go - long currentDateTimeTicks = DateTime.UtcNow.Ticks; - long currentStopwatchTicks = Ticks; - - // Calculate the number of timer ticks that have passed since the last check and - // extrapolate the local date/time we should be expecting to see - long ticksSinceLastCheck = currentStopwatchTicks - lastCheckedStopwatchTicks; - long expectedLocalTimeTicks = this.lastCheckedDateTimeTicks + ticksSinceLastCheck; - - // Find out by what amount the actual local date/time deviates from - // the extrapolated date/time and trigger the date/time adjustment event if - // we can reasonably assume that the system date/time have been adjusted. - long deviationTicks = Math.Abs(expectedLocalTimeTicks - currentDateTimeTicks); - if(deviationTicks > TimeAdjustmentToleranceTicks) { - OnDateTimeAdjusted(this, EventArgs.Empty); - } - - // Remember the current local date/time and timer ticks for the next run - this.lastCheckedDateTimeTicks = currentDateTimeTicks; - this.lastCheckedStopwatchTicks = currentStopwatchTicks; - - } - - /// Last local time we checked for a date/time adjustment - private long lastCheckedDateTimeTicks; - /// Timer ticks at which we last checked the local time - private long lastCheckedStopwatchTicks; - - /// Number of ticks per Stopwatch time unit - private static double tickFrequency; - /// Whether ot use the Stopwatch class for measuring time - private bool useStopwatch; - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/IAbortable.cs b/Source/Scheduling/IAbortable.cs deleted file mode 100644 index b031703..0000000 --- a/Source/Scheduling/IAbortable.cs +++ /dev/null @@ -1,42 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Scheduling { - - /// Interface for abortable processes - public interface IAbortable { - - /// Aborts the running process. Can be called from any thread. - /// - /// The receive should honor the abort request and stop whatever it is - /// doing as soon as possible. The method does not impose any requirement - /// on the timeliness of the reaction of the running process, but implementers - /// are advised to not ignore the abort request and urged to try and design - /// their code in such a way that it can be stopped in a reasonable time - /// (eg. within 1 second of the abort request). - /// - void AsyncAbort(); - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/ISchedulerService.cs b/Source/Scheduling/ISchedulerService.cs deleted file mode 100644 index e5e3c0f..0000000 --- a/Source/Scheduling/ISchedulerService.cs +++ /dev/null @@ -1,111 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Nuclex.Support.Scheduling { - -#if SCHEDULER_USE_CUSTOM_CALLBACK - /// Signature for a timed callback from the scheduler service - public delegate void SchedulerCallback(); -#endif // SCHEDULER_USE_CUSTOM_CALLBACK - - /// Service that allows the scheduled invocation of tasks - public interface ISchedulerService { - - /// Time source being used by the scheduler - ITimeSource TimeSource { get; } - - /// Schedules a notification at the specified absolute time - /// - /// Absolute time at which the notification will occur - /// - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - /// - /// The notification is scheduled for the indicated absolute time. If the system - /// enters/leaves daylight saving time or the date/time is changed (for example - /// when the system synchronizes with an NTP server), this will affect - /// the notification. So if you need to be notified after a fixed time, use - /// the NotifyIn() method instead. - /// - object NotifyAt(DateTime notificationTime, WaitCallback callback); - - /// - /// Schedules a recurring notification after the specified amount of milliseconds - /// - /// - /// Milliseconds after which the first notification will occur - /// - /// - /// Interval in milliseconds at which the notification will be repeated - /// - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - object NotifyEach( - int delayMilliseconds, int intervalMilliseconds, WaitCallback callback - ); - - /// - /// Schedules a recurring notification after the specified time span - /// - /// Delay after which the first notification will occur - /// Interval at which the notification will be repeated - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - object NotifyEach(TimeSpan delay, TimeSpan interval, WaitCallback callback); - - /// - /// Schedules a notification after the specified amount of milliseconds - /// - /// - /// Number of milliseconds after which the notification will occur - /// - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - object NotifyIn(int delayMilliseconds, WaitCallback callback); - - /// Schedules a notification after the specified time span - /// Delay after which the notification will occur - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - object NotifyIn(TimeSpan delay, WaitCallback callback); - - /// Cancels a scheduled notification - /// - /// Handle of the notification that will be cancelled - /// - void Cancel(object notificationHandle); - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/ITimeSource.cs b/Source/Scheduling/ITimeSource.cs deleted file mode 100644 index 25087d0..0000000 --- a/Source/Scheduling/ITimeSource.cs +++ /dev/null @@ -1,67 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Nuclex.Support.Scheduling { - - /// Provides time measurement and change notification services - public interface ITimeSource { - - /// Called when the system date/time are adjusted - /// - /// An adjustment is a change out of the ordinary, eg. when a time synchronization - /// alters the current system time, when daylight saving time takes effect or - /// when the user manually adjusts the system date/time. - /// - event EventHandler DateTimeAdjusted; - - /// Waits for an AutoResetEvent to become signalled - /// WaitHandle the method will wait for - /// Number of ticks to wait - /// - /// True if the WaitHandle was signalled, false if the timeout was reached - /// or the time source thinks its time to recheck the system date/time. - /// - /// - /// Depending on whether the system will provide notifications when date/time - /// is adjusted, the time source will be forced to let this method block for - /// less than the indicated time before returning a timeout in order to give - /// the caller a chance to recheck the system time. - /// - bool WaitOne(AutoResetEvent waitHandle, long ticks); - - /// Current system time in UTC format - DateTime CurrentUtcTime { get; } - - /// How long the time source has been running - /// - /// There is no guarantee this value starts at zero (or anywhere near it) when - /// the time source is created. The only requirement for this value is that it - /// keeps increasing with the passing of time and that it stays unaffected - /// (eg. doesn't skip or jump back) when the system date/time are changed. - /// - long Ticks { get; } - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/Operation.Test.cs b/Source/Scheduling/Operation.Test.cs deleted file mode 100644 index e493bbf..0000000 --- a/Source/Scheduling/Operation.Test.cs +++ /dev/null @@ -1,68 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; - -using NUnit.Framework; - -namespace Nuclex.Support.Scheduling { - - /// Unit Test for the operation class - [TestFixture] - public class OperationTest { - - #region class TestOperation - - /// Dummy operation used to run the unit tests - private class TestOperation : Operation { - - /// Launches the background operation - public override void Start() { - // This could become a race condition of this code would be used in a fashion - // different than what current unit tests do with it - if(!base.Ended) { - OnAsyncEnded(); - } - } - - } - - #endregion // class TestOperation - - /// Tests whether operations can be started - [Test] - public void TestOperationStarting() { - TestOperation myOperation = new TestOperation(); - - Assert.IsFalse(myOperation.Ended); - myOperation.Start(); - Assert.IsTrue(myOperation.Ended); - } - - } - -} // namespace Nuclex.Support.Scheduling - -#endif // UNITTEST diff --git a/Source/Scheduling/Operation.cs b/Source/Scheduling/Operation.cs deleted file mode 100644 index c62581b..0000000 --- a/Source/Scheduling/Operation.cs +++ /dev/null @@ -1,36 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -using Nuclex.Support.Tracking; - -namespace Nuclex.Support.Scheduling { - - /// Base class for observable operations running in the background - public abstract class Operation : Request { - - /// Launches the background operation - public abstract void Start(); - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/OperationQueue.Test.cs b/Source/Scheduling/OperationQueue.Test.cs deleted file mode 100644 index 15e0b6b..0000000 --- a/Source/Scheduling/OperationQueue.Test.cs +++ /dev/null @@ -1,350 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.IO; - -using NUnit.Framework; -using NMock; - -using Nuclex.Support.Tracking; - -namespace Nuclex.Support.Scheduling { - - /// Unit Test for the operation queue class - [TestFixture] - public class OperationQueueTest { - - #region interface IOperationQueueSubscriber - - /// Interface used to test the operation queue - public interface IOperationQueueSubscriber { - - /// Called when the operations queue's progress changes - /// Operation queue whose progress has changed - /// Contains the new progress achieved - void ProgressChanged(object sender, ProgressReportEventArgs arguments); - - /// Called when the operation queue has ended - /// Operation queue that as ended - /// Not used - void Ended(object sender, EventArgs arguments); - - } - - #endregion // interface IOperationQueueSubscriber - - #region class ProgressUpdateEventArgsMatcher - - /// Compares two ProgressUpdateEventArgsInstances for NMock validation - private class ProgressUpdateEventArgsMatcher : Matcher { - - /// Initializes a new ProgressUpdateEventArgsMatcher - /// Expected progress update event arguments - public ProgressUpdateEventArgsMatcher(ProgressReportEventArgs expected) { - this.expected = expected; - } - - /// - /// Called by NMock to verfiy the ProgressUpdateEventArgs match the expected value - /// - /// Actual value to compare to the expected value - /// - /// True if the actual value matches the expected value; otherwise false - /// - public override bool Matches(object actualAsObject) { - ProgressReportEventArgs actual = (actualAsObject as ProgressReportEventArgs); - if(actual == null) - return false; - - return (actual.Progress == this.expected.Progress); - } - - /// Creates a string representation of the expected value - /// Writer to write the string representation into - public override void DescribeTo(TextWriter writer) { - writer.Write(this.expected.Progress.ToString()); - } - - /// Expected progress update event args value - private ProgressReportEventArgs expected; - - } - - #endregion // class ProgressUpdateEventArgsMatcher - - #region class TestOperation - - /// Operation used for testing in this unit test - private class TestOperation : Operation, IProgressReporter { - - /// will be triggered to report when progress has been achieved - public event EventHandler AsyncProgressChanged; - - /// Begins executing the operation. Yeah, sure :) - public override void Start() { } - - /// Moves the operation into the ended state - public void SetEnded() { - SetEnded(null); - } - - /// Moves the operation into the ended state with an exception - /// Exception - public void SetEnded(Exception exception) { - this.exception = exception; - OnAsyncEnded(); - } - - /// Changes the testing operation's indicated progress - /// - /// New progress to be reported by the testing operation - /// - public void ChangeProgress(float progress) { - OnAsyncProgressChanged(progress); - } - - /// - /// Allows the specific request implementation to re-throw an exception if - /// the background process finished unsuccessfully - /// - protected override void ReraiseExceptions() { - if(this.exception != null) - throw this.exception; - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this operation about the achieved progress. - /// - protected virtual void OnAsyncProgressChanged(float progress) { - OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this operation about the achieved progress. - /// Allows for classes derived from the Operation class to easily provide - /// a custom event arguments class that has been derived from the - /// operation's ProgressUpdateEventArgs class. - /// - protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { - EventHandler copy = AsyncProgressChanged; - if(copy != null) - copy(this, eventArguments); - } - - /// Exception that has occured in the background process - private volatile Exception exception; - - } - - #endregion // class TestOperation - - /// Initialization routine executed before each test is run - [SetUp] - public void Setup() { - this.mockery = new MockFactory(); - } - - /// Validates that the queue executes operations sequentially - [Test] - public void TestSequentialExecution() { - TestOperation operation1 = new TestOperation(); - TestOperation operation2 = new TestOperation(); - - OperationQueue testQueueOperation = - new OperationQueue( - new TestOperation[] { operation1, operation2 } - ); - - Mock mockedSubscriber = mockSubscriber(testQueueOperation); - - testQueueOperation.Start(); - - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new Matcher[] { - new NMock.Matchers.TypeMatcher(typeof(OperationQueue)), - new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f)) - } - ); - - operation1.ChangeProgress(0.5f); - - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new Matcher[] { - new NMock.Matchers.TypeMatcher(typeof(OperationQueue)), - new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f)) - } - ); - - operation1.SetEnded(); - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - - /// - /// Validates that the queue executes operations sequentially and honors the weights - /// assigned to the individual operations - /// - [Test] - public void TestWeightedSequentialExecution() { - TestOperation operation1 = new TestOperation(); - TestOperation operation2 = new TestOperation(); - - OperationQueue testQueueOperation = - new OperationQueue( - new WeightedTransaction[] { - new WeightedTransaction(operation1, 0.5f), - new WeightedTransaction(operation2, 2.0f) - } - ); - - Mock mockedSubscriber = mockSubscriber(testQueueOperation); - - testQueueOperation.Start(); - - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(OperationQueue)), - new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.1f)) - ); - - operation1.ChangeProgress(0.5f); - - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(OperationQueue)), - new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.2f)) - ); - - operation1.SetEnded(); - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - - /// - /// Validates that the operation queue propagates the ended event once all contained - /// operations have completed - /// - [Test] - public void TestEndPropagation() { - TestOperation operation1 = new TestOperation(); - TestOperation operation2 = new TestOperation(); - - OperationQueue testQueueOperation = - new OperationQueue( - new TestOperation[] { - operation1, - operation2 - } - ); - - testQueueOperation.Start(); - - Assert.IsFalse(testQueueOperation.Ended); - operation1.SetEnded(); - Assert.IsFalse(testQueueOperation.Ended); - operation2.SetEnded(); - Assert.IsTrue(testQueueOperation.Ended); - - testQueueOperation.Join(); - } - - /// - /// Validates that the operation queue delivers an exception occuring in one of the - /// contained operations to the operation queue joiner - /// - [Test] - public void TestExceptionPropagation() { - TestOperation operation1 = new TestOperation(); - TestOperation operation2 = new TestOperation(); - - OperationQueue testQueueOperation = - new OperationQueue( - new TestOperation[] { - operation1, - operation2 - } - ); - - testQueueOperation.Start(); - - Assert.IsFalse(testQueueOperation.Ended); - operation1.SetEnded(); - Assert.IsFalse(testQueueOperation.Ended); - operation2.SetEnded(new AbortedException("Hello World")); - - Assert.Throws( - delegate() { testQueueOperation.Join(); } - ); - } - - /// - /// Ensures that the operation queue transparently wraps the child operations in - /// an observation wrapper that is not visible to an outside user - /// - [Test] - public void TestTransparentWrapping() { - WeightedTransaction operation1 = new WeightedTransaction( - new TestOperation() - ); - WeightedTransaction operation2 = new WeightedTransaction( - new TestOperation() - ); - - OperationQueue testQueueOperation = - new OperationQueue( - new WeightedTransaction[] { - operation1, - operation2 - } - ); - - // Order is important due to sequential execution! - Assert.AreSame(operation1, testQueueOperation.Children[0]); - Assert.AreSame(operation2, testQueueOperation.Children[1]); - } - - /// Mocks a subscriber for the events of an operation - /// Operation to mock an event subscriber for - /// The mocked event subscriber - private Mock mockSubscriber(Operation operation) { - Mock mockedSubscriber = - this.mockery.CreateMock(); - - operation.AsyncEnded += new EventHandler(mockedSubscriber.MockObject.Ended); - (operation as IProgressReporter).AsyncProgressChanged += - new EventHandler(mockedSubscriber.MockObject.ProgressChanged); - - return mockedSubscriber; - } - - /// Mock object factory - private MockFactory mockery; - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Scheduling/OperationQueue.cs b/Source/Scheduling/OperationQueue.cs deleted file mode 100644 index 760c57c..0000000 --- a/Source/Scheduling/OperationQueue.cs +++ /dev/null @@ -1,242 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Threading; - -using Nuclex.Support.Tracking; - -namespace Nuclex.Support.Scheduling { - - /// Operation that sequentially executes a series of operations - /// - /// Type of the child operations the QueueOperation will contain - /// - public class OperationQueue : Operation, IProgressReporter - where OperationType : Operation { - - /// will be triggered to report when progress has been achieved - public event EventHandler AsyncProgressChanged; - - /// Initializes a new queue operation with default weights - /// Child operations to execute in this operation - /// - /// All child operations will have a default weight of 1.0 - /// - public OperationQueue(IEnumerable childs) : this() { - - // Construct a WeightedTransaction with the default weight for each - // transaction and wrap it in an ObservedTransaction - foreach(OperationType operation in childs) - this.children.Add(new WeightedTransaction(operation)); - - // Since all transactions have a weight of 1.0, the total weight is - // equal to the number of transactions in our list - this.totalWeight = (float)this.children.Count; - - } - - /// Initializes a new queue operation with custom weights - /// Child operations to execute in this operation - public OperationQueue(IEnumerable> childs) : this() { - - // Construct an ObservedTransactionn around each of the WeightedTransactions - foreach(WeightedTransaction operation in childs) { - this.children.Add(operation); - - // Sum up the total weight - this.totalWeight += operation.Weight; - } - - } - - /// Initializes a new queue operation - private OperationQueue() { - this.asyncOperationEndedDelegate = new EventHandler(asyncOperationEnded); - this.asyncOperationProgressChangedDelegate = new EventHandler( - asyncOperationProgressChanged - ); - - this.children = new List>(); - } - - /// Provides access to the child operations of this queue - public IList> Children { - get { return this.children; } - } - - /// Launches the background operation - public override void Start() { - startCurrentOperation(); - } - - /// - /// Allows the specific request implementation to re-throw an exception if - /// the background process finished unsuccessfully - /// - protected override void ReraiseExceptions() { - if(this.exception != null) - throw this.exception; - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this transaction about the achieved progress. - /// - protected virtual void OnAsyncProgressChanged(float progress) { - OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this transaction about the achieved progress. - /// Allows for classes derived from the transaction class to easily provide - /// a custom event arguments class that has been derived from the - /// transaction's ProgressUpdateEventArgs class. - /// - protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { - EventHandler copy = AsyncProgressChanged; - if(copy != null) - copy(this, eventArguments); - } - - /// Prepares the current operation and calls its Start() method - /// - /// This subscribes the queue to the events of to the current operation - /// and launches the operation by calling its Start() method. - /// - private void startCurrentOperation() { - do { - Thread.MemoryBarrier(); - OperationType operation = this.children[this.currentOperationIndex].Transaction; - - operation.AsyncEnded += this.asyncOperationEndedDelegate; - - IProgressReporter progressReporter = operation as IProgressReporter; - if(progressReporter != null) - progressReporter.AsyncProgressChanged += this.asyncOperationProgressChangedDelegate; - - Interlocked.Exchange(ref this.completionStatus, 1); - operation.Start(); - } while(Interlocked.Decrement(ref this.completionStatus) > 0); - } - - /// Disconnects from the current operation and calls its End() method - /// - /// This unsubscribes the queue from the current operation's events, calls End() - /// on the operation and, if the operation didn't have an exception to report, - /// counts up the accumulated progress of th e queue. - /// - private void endCurrentOperation() { - Thread.MemoryBarrier(); - OperationType operation = this.children[this.currentOperationIndex].Transaction; - - // Disconnect from the operation's events - operation.AsyncEnded -= this.asyncOperationEndedDelegate; - - IProgressReporter progressReporter = operation as IProgressReporter; - if(progressReporter != null) - progressReporter.AsyncProgressChanged -= this.asyncOperationProgressChangedDelegate; - - try { - operation.Join(); - - // Add the operations weight to the total amount of completed weight in the queue - this.completedWeight += this.children[this.currentOperationIndex].Weight; - - // Trigger another progress update - OnAsyncProgressChanged(this.completedWeight / this.totalWeight); - } - catch(Exception exception) { - this.exception = exception; - } - } - - /// Called when the current executing operation ends - /// Operation that ended - /// Not used - private void asyncOperationEnded(object sender, EventArgs arguments) { - - // Unsubscribe from the current operation's events and update the - // accumulating progress counter - endCurrentOperation(); - - // Only jump to the next operation if no exception occured - if(this.exception == null) { - int newIndex = Interlocked.Increment(ref this.currentOperationIndex); - Thread.MemoryBarrier(); - - // Execute the next operation unless we reached the end of the queue - if(newIndex < this.children.Count) { - if(Interlocked.Increment(ref this.completionStatus) == 1) { - startCurrentOperation(); - } - return; - } - } - - // Either an exception has occured or we reached the end of the operation - // queue. In any case, we need to report that the operation is over. - OnAsyncEnded(); - - } - - /// Called when currently executing operation makes progress - /// Operation that has achieved progress - /// Not used - private void asyncOperationProgressChanged( - object sender, ProgressReportEventArgs arguments - ) { - - // Determine the completed weight of the currently executing operation - float operationWeight = this.children[this.currentOperationIndex].Weight; - float operationCompletedWeight = arguments.Progress * operationWeight; - - // Build the total normalized amount of progress for the queue - float progress = (this.completedWeight + operationCompletedWeight) / this.totalWeight; - - // Done, we can send the actual progress to any event subscribers - OnAsyncProgressChanged(progress); - - } - - /// Delegate to the asyncOperationEnded() method - private EventHandler asyncOperationEndedDelegate; - /// Delegate to the asyncOperationProgressUpdated() method - private EventHandler asyncOperationProgressChangedDelegate; - /// Operations being managed in the queue - private List> children; - /// Summed weight of all operations in the queue - private float totalWeight; - /// Accumulated weight of the operations already completed - private float completedWeight; - /// Index of the operation currently executing - private int currentOperationIndex; - /// Used to detect when an operation completes synchronously - private int completionStatus; - /// Exception that has occured in the background process - private volatile Exception exception; - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/Scheduler.Test.cs b/Source/Scheduling/Scheduler.Test.cs deleted file mode 100644 index d71a714..0000000 --- a/Source/Scheduling/Scheduler.Test.cs +++ /dev/null @@ -1,489 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading; - -using Microsoft.Win32; - -using NUnit.Framework; - -namespace Nuclex.Support.Scheduling { - - /// Unit Test for the scheduler - [TestFixture] - public class SchedulerTest { - - #region class MockTimeSource - - /// Mocked time source - private class MockTimeSource : ITimeSource { - - /// Called when the system date/time are adjusted - public event EventHandler DateTimeAdjusted; - - /// Initializes a new mocked time source - /// Start time in UTC format - public MockTimeSource(DateTime utcStartTime) { - this.currentTime = utcStartTime; - this.currentTicks = 1000000000; - } - - /// Waits for an AutoResetEvent to become signalled - /// WaitHandle the method will wait for - /// Number of ticks to wait - /// - /// True if the WaitHandle was signalled, false if the timeout was reached - /// or the time source thinks its time to recheck the system date/time. - /// - public bool WaitOne(AutoResetEvent waitHandle, long ticks) { - long currentTicks; - long eventDueTicks; - lock(this) { - this.autoResetEvent = waitHandle; - this.eventDueTicks += ticks; - - currentTicks = this.currentTicks; - eventDueTicks = this.eventDueTicks; - } - - // If we need to wait, use the wait handle. We do not use the wait handle's - // return value (or even its timeout) because we might trigger it ourselves - // to simulate the passing of time. - if(eventDueTicks > 0) { - this.autoResetEvent = waitHandle; - waitHandle.WaitOne(); - this.autoResetEvent = null; - } - - // Do not use the cached values here -- we might have used the WaitHandle and - // the simulation time could have been advanced while we were waiting. - lock(this) { - return (this.eventDueTicks > 0); // True = signalled, false = timed out - } - } - - /// Current system time in UTC format - public DateTime CurrentUtcTime { - get { lock(this) { return this.currentTime; } } - } - - /// How long the time source has been running - public long Ticks { - get { - lock(this) { - this.eventDueTicks = 0; - return this.currentTicks; - } - } - } - - /// Advances the time of the time source - /// - /// Time span by which to advance the time source's time - /// - public void AdvanceTime(TimeSpan timeSpan) { - lock(this) { - this.currentTicks += timeSpan.Ticks; - this.currentTime += timeSpan; - - // Problem: The Scheduler has just calculated the remaining ticks until - // notification occurs. Next, another thread advances simulation time - // and then the scheduler calls this. It will wait, even though - // the simultion time has progressed. - // To compensate this, we track the remaining time until the event is due - // and allow for a negative time budget if AdvanceTime() is called after - // the scheduler has just queried the current tick count. - this.eventDueTicks -= timeSpan.Ticks; - - if(this.eventDueTicks <= 0) { - AutoResetEvent copy = this.autoResetEvent; - if(copy != null) { - copy.Set(); - } - } - } - } - - /// Manually triggers the date time adjusted event - /// New simulation time to jump to - public void AdjustTime(DateTime newUtcTime) { - lock(this) { - this.currentTime = newUtcTime; - } - EventHandler copy = DateTimeAdjusted; - if(copy != null) { - copy(this, EventArgs.Empty); - } - } - - /// Auto reset event the time source is currently waiting on - private volatile AutoResetEvent autoResetEvent; - /// Ticks at which the auto reset event will be due - private long eventDueTicks; - /// Current time source tick counter - private long currentTicks; - /// Current system time and date - private DateTime currentTime; - - } - - #endregion // class MockTimeSource - - #region class TestSubscriber - - /// Subscriber used to test the scheduler notifications - private class TestSubscriber : IDisposable { - - /// Initializes a new test subscriber - public TestSubscriber() { - this.waitHandle = new AutoResetEvent(false); - } - - /// Immediately releases all resources owned by the instance - public void Dispose() { - if(this.waitHandle != null) { - this.waitHandle.Close(); - this.waitHandle = null; - } - } - - /// Callback method that can be subscribed to the scheduler - /// Not used - public void Callback(object state) { - Interlocked.Increment(ref this.callbackCount); - this.waitHandle.Set(); - } - - /// Blocks ther caller until the callback is invoked - /// - /// Maximum number of milliseconds to wait for the callback - /// - /// True if the callback was invoked, false if the call timed out - public bool WaitForCallback(int milliseconds) { - return this.waitHandle.WaitOne(milliseconds); - } - - /// Number of times the callback has been invoked - public int CallbackCount { - get { return Thread.VolatileRead(ref this.callbackCount); } - } - - /// Callback invocation count - private int callbackCount; - /// WaitHandle used to wait for the callback - private AutoResetEvent waitHandle; - - } - - #endregion // class TestSubscriber - - /// - /// Test whether the Scheduler can explicitely create a windows time source - /// - [Test] - public void TestCreateWindowsTimeSource() { - ITimeSource timeSource = Scheduler.CreateTimeSource(true); - try { - Assert.That(timeSource is WindowsTimeSource); - } finally { - IDisposable disposableTimeSource = timeSource as IDisposable; - if(disposableTimeSource != null) { - disposableTimeSource.Dispose(); - } - } - } - - /// - /// Test whether the Scheduler can explicitely create a generic time source - /// - [Test] - public void TestCreateGenericTimeSource() { - ITimeSource timeSource = Scheduler.CreateTimeSource(false); - try { - Assert.That(timeSource is GenericTimeSource); - } finally { - IDisposable disposableTimeSource = timeSource as IDisposable; - if(disposableTimeSource != null) { - disposableTimeSource.Dispose(); - } - } - } - - /// - /// Test whether the Scheduler can automatically choose the right time source - /// - [Test] - public void TestCreateDefaultTimeSource() { - ITimeSource timeSource = Scheduler.CreateDefaultTimeSource(); - try { - Assert.IsNotNull(timeSource); - } finally { - IDisposable disposableTimeSource = timeSource as IDisposable; - if(disposableTimeSource != null) { - disposableTimeSource.Dispose(); - } - } - } - - /// - /// Verifies that the default constructor of the scheduler is working - /// - [Test] - public void TestDefaultConstructor() { - using(Scheduler scheduler = new Scheduler()) { } - } - - /// - /// Verifies that the default constructor of the scheduler is working - /// - [Test] - public void TestThrowOnNotifyAtWithUnspecifiedDateTimeKind() { - using(TestSubscriber subscriber = new TestSubscriber()) { - using(Scheduler scheduler = new Scheduler()) { - Assert.Throws( - delegate() { - scheduler.NotifyAt(new DateTime(2000, 1, 1), subscriber.Callback); - } - ); - } - } - } - - /// - /// Tests whether the NotifyAt() method invokes the callback at the right time - /// - [Test] - public void TestNotifyAt() { - MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1)); - using(TestSubscriber subscriber = new TestSubscriber()) { - using(Scheduler scheduler = new Scheduler(mockTimeSource)) { - scheduler.NotifyAt(makeUtc(new DateTime(2010, 1, 2)), subscriber.Callback); - - mockTimeSource.AdvanceTime(TimeSpan.FromDays(1)); - - Assert.IsTrue(subscriber.WaitForCallback(1000)); - } - } - } - - /// - /// Verifies that a notification at an absolute time is processed correctly - /// if a time synchronization occurs during the wait. - /// - [Test] - public void TestNotifyAtWithDateTimeAdjustment() { - MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1)); - using(TestSubscriber subscriber = new TestSubscriber()) { - using(Scheduler scheduler = new Scheduler(mockTimeSource)) { - scheduler.NotifyAt(makeUtc(new DateTime(2010, 1, 2)), subscriber.Callback); - - // Let 12 hours pass, after that, we simulate a time synchronization - // that puts the system 12 hours ahead of the original time. - mockTimeSource.AdvanceTime(TimeSpan.FromHours(12)); - mockTimeSource.AdjustTime(new DateTime(2010, 1, 2)); - - Assert.IsTrue(subscriber.WaitForCallback(1000)); - } - } - } - - /// Tests whether the scheduler's Cancel() method is working - [Test] - public void TestCancelNotification() { - MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1)); - using(TestSubscriber subscriber1 = new TestSubscriber()) { - using(TestSubscriber subscriber2 = new TestSubscriber()) { - using(Scheduler scheduler = new Scheduler(mockTimeSource)) { - object handle = scheduler.NotifyIn( - TimeSpan.FromHours(24), subscriber1.Callback - ); - scheduler.NotifyIn(TimeSpan.FromHours(36), subscriber2.Callback); - - mockTimeSource.AdvanceTime(TimeSpan.FromHours(12)); - scheduler.Cancel(handle); - mockTimeSource.AdvanceTime(TimeSpan.FromHours(24)); - - // Wait for the second subscriber to be notified. This is still a race - // condition (there's no guarantee the thread pool will run tasks in - // the order they were added), but it's the best we can do without - // relying on an ugly Thread.Sleep() in this test. - Assert.IsTrue(subscriber2.WaitForCallback(1000)); - Assert.AreEqual(0, subscriber1.CallbackCount); - } - } - } - } - - /// - /// Tests the scheduler with two notifications that are scheduled in inverse - /// order of their due time. - /// - [Test] - public void TestInverseOrderNotification() { - MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1)); - using(TestSubscriber subscriber1 = new TestSubscriber()) { - using(TestSubscriber subscriber2 = new TestSubscriber()) { - using(Scheduler scheduler = new Scheduler(mockTimeSource)) { - scheduler.NotifyIn(TimeSpan.FromHours(24), subscriber1.Callback); - scheduler.NotifyIn(TimeSpan.FromHours(12), subscriber2.Callback); - - mockTimeSource.AdvanceTime(TimeSpan.FromHours(18)); - - Assert.IsTrue(subscriber2.WaitForCallback(1000)); - Assert.AreEqual(0, subscriber1.CallbackCount); - - mockTimeSource.AdvanceTime(TimeSpan.FromHours(18)); - - Assert.IsTrue(subscriber1.WaitForCallback(1000)); - } - } - } - } - - /// - /// Tests the scheduler with two notifications that are scheduled to - /// occur at the exact same time - /// - [Test] - public void TestTwoNotificationsAtSameTime() { - MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1)); - using(TestSubscriber subscriber1 = new TestSubscriber()) { - using(TestSubscriber subscriber2 = new TestSubscriber()) { - using(Scheduler scheduler = new Scheduler(mockTimeSource)) { - scheduler.NotifyIn(60000, subscriber1.Callback); - scheduler.NotifyIn(60000, subscriber2.Callback); - - mockTimeSource.AdvanceTime(TimeSpan.FromMilliseconds(60000)); - - Assert.IsTrue(subscriber1.WaitForCallback(1000)); - Assert.IsTrue(subscriber2.WaitForCallback(1000)); - } - } - } - } - - /// - /// Verifies that the scheduler's NotifyEach() method is working correctly - /// - [Test] - public void TestNotifyEachWithMilliseconds() { - MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1)); - using(TestSubscriber subscriber = new TestSubscriber()) { - using(Scheduler scheduler = new Scheduler(mockTimeSource)) { - scheduler.NotifyEach(1000, 1000, subscriber.Callback); - - mockTimeSource.AdvanceTime(TimeSpan.FromMilliseconds(4000)); - - // Wait for 4 invocations of the callback. We might not catch each trigger - // of the AutoResetEvent, but we know that it will be 4 at most. - for(int invocation = 0; invocation < 4; ++invocation) { - Assert.IsTrue(subscriber.WaitForCallback(1000)); - - if(subscriber.CallbackCount == 4) { - break; - } - } - } - } - } - - /// - /// Verifies that the scheduler's NotifyEach() method is working correctly - /// - [Test] - public void TestNotifyEachWithTimespan() { - MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1)); - using(TestSubscriber subscriber = new TestSubscriber()) { - using(Scheduler scheduler = new Scheduler(mockTimeSource)) { - scheduler.NotifyEach( - TimeSpan.FromHours(12), TimeSpan.FromHours(1), subscriber.Callback - ); - - mockTimeSource.AdvanceTime(TimeSpan.FromHours(14)); - - // Wait for 3 invocations of the callback. We might not catch each trigger - // of the AutoResetEvent, but we know that it will be 3 at most. - for(int invocation = 0; invocation < 3; ++invocation) { - Assert.IsTrue(subscriber.WaitForCallback(1000)); - - if(subscriber.CallbackCount == 3) { - break; - } - } - } - } - } - - /// - /// Reproduction case for a bug that occurred when the final notification in - /// the scheduler was cancelled (call to PriorityQueue.Peek() on empty queue) - /// - [Test] - public void TestCancelFinalNotification() { - MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1)); - using(TestSubscriber subscriber = new TestSubscriber()) { - using(Scheduler scheduler = new Scheduler(mockTimeSource)) { - scheduler.Cancel( - scheduler.NotifyIn(TimeSpan.FromHours(12), subscriber.Callback) - ); - - mockTimeSource.AdvanceTime(TimeSpan.FromHours(14)); - Thread.Sleep(1); - } - } - } - - // TODO: Unit testing caused this exception - // - // Nuclex.Support.Scheduling.SchedulerTest.TestThrowOnNotifyAtWithUnspecifiedDateTimeKind : - // System.NullReferenceException: Der Objektverweis wurde nicht auf - // eine Objektinstanz festgelegt. - // bei Nuclex.Support.Scheduling.SchedulerTest.TestSubscriber.Callback(Object state) - // in D:\Devel\framework\Nuclex.Support\Source\Scheduling\Scheduler.Test.cs:Zeile 177. - // bei System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state) - // bei System.Threading.ExecutionContext.Run( - // ExecutionContext executionContext, ContextCallback callback, Object state - // ) - // bei System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal( - // _ThreadPoolWaitCallback tpWaitCallBack - // ) - // bei System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state) - - /// Returns the provided date/time value as a utc time value - /// Date/time value that will be returned as UTC - /// The provided date/time value as UTC - /// - /// This doesn't convert the time, it just returns the exact same date and time - /// but tags it as UTC by setting the DateTimeKind to UTC. - /// - private static DateTime makeUtc(DateTime dateTime) { - return new DateTime(dateTime.Ticks, DateTimeKind.Utc); - } - - } - -} // namespace Nuclex.Support.Scheduling - -#endif // UNITTEST diff --git a/Source/Scheduling/Scheduler.TimeSource.cs b/Source/Scheduling/Scheduler.TimeSource.cs deleted file mode 100644 index 3ea4571..0000000 --- a/Source/Scheduling/Scheduler.TimeSource.cs +++ /dev/null @@ -1,79 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Diagnostics; - -using Nuclex.Support.Collections; - -namespace Nuclex.Support.Scheduling { - - /// Schedules actions for execution at a future point in time - partial class Scheduler { - - #region class TimeSourceSingleton - - /// - /// Manages the singleton instance of the scheduler's default time source - /// - private class TimeSourceSingleton { - - /// - /// Explicit static constructor to guarantee the singleton is initialized only - /// when a static member of this class is accessed. - /// - static TimeSourceSingleton() { } // Do not remove! - - /// The singleton instance of the default time source - internal static readonly ITimeSource Instance = Scheduler.CreateDefaultTimeSource(); - - } - - #endregion // class TimeSourceSingleton - - /// Returns the default time source for the scheduler - public static ITimeSource DefaultTimeSource { - get { return TimeSourceSingleton.Instance; } - } - - /// Creates a new default time source for the scheduler - /// - /// Whether the specialized windows time source should be used - /// - /// The newly created time source - internal static ITimeSource CreateTimeSource(bool useWindowsTimeSource) { - if(useWindowsTimeSource) { - return new WindowsTimeSource(); - } else { - return new GenericTimeSource(); - } - } - - /// Creates a new default time source for the scheduler - /// The newly created time source - internal static ITimeSource CreateDefaultTimeSource() { - return CreateTimeSource(WindowsTimeSource.Available); - } - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/Scheduler.cs b/Source/Scheduling/Scheduler.cs deleted file mode 100644 index 5fb30c3..0000000 --- a/Source/Scheduling/Scheduler.cs +++ /dev/null @@ -1,460 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Diagnostics; - -using Nuclex.Support.Collections; - -namespace Nuclex.Support.Scheduling { - - /// Schedules actions for execution at a future point in time - public partial class Scheduler : ISchedulerService, IDisposable { - - /// One tick is 100 ns, meaning 10000 ticks equal 1 ms - private const long TicksPerMillisecond = 10000; - - #region class Notification - - /// Scheduled notification - private class Notification { - - /// Initializes a new notification - /// - /// Interval in which the notification will re-executed - /// - /// - /// Time source ticks the notification is next due at - /// - /// - /// Absolute time in UTC at which the notification is due - /// - /// - /// Callback to be invoked when the notification is due - /// - public Notification( - long intervalTicks, - long nextDueTicks, - DateTime absoluteUtcTime, - WaitCallback callback - ) { - this.IntervalTicks = intervalTicks; - this.NextDueTicks = nextDueTicks; - this.AbsoluteUtcTime = absoluteUtcTime; - this.Callback = callback; - this.Cancelled = false; - } - - /// - /// Ticks specifying the interval in which the notification will be re-executed - /// - public long IntervalTicks; - - /// Next due time for this notification - public long NextDueTicks; - /// Absolute time in UTC at which the notification is due - /// - /// Only stored for notifications scheduled in absolute time, meaning they - /// have to be adjusted if the system date/time changes - /// - public DateTime AbsoluteUtcTime; - /// Callback that will be invoked when the notification is due - public WaitCallback Callback; - /// Whether the notification has been cancelled - public bool Cancelled; - - } - - #endregion // class Notification - - #region class NotificationComparer - - /// Compares two notifications to each other - private class NotificationComparer : IComparer { - - /// The default instance of the notification comparer - public static readonly NotificationComparer Default = new NotificationComparer(); - - /// Compares two notifications to each other based on their time - /// Notification that will be compared on the left side - /// Notification that will be comapred on the right side - /// The relation of the two notification's times to each other - public int Compare(Notification left, Notification right) { - if(left.NextDueTicks > right.NextDueTicks) { - return -1; - } else if(left.NextDueTicks < right.NextDueTicks) { - return 1; - } else { - return 0; - } - } - - } - - #endregion // class NotificationComparer - - /// Initializes a new scheduler using the default time source - public Scheduler() : this(DefaultTimeSource) { } - - /// Initializes a new scheduler using the specified time source - /// Time source the scheduler will use - public Scheduler(ITimeSource timeSource) { - this.dateTimeAdjustedDelegate = new EventHandler(dateTimeAdjusted); - - this.timeSource = timeSource; - this.timeSource.DateTimeAdjusted += this.dateTimeAdjustedDelegate; - - this.notifications = new PriorityQueue(NotificationComparer.Default); - this.notificationWaitEvent = new AutoResetEvent(false); - - this.timerThread = new Thread(new ThreadStart(runTimerThread)); - this.timerThread.Name = "Nuclex.Support.Scheduling.Scheduler"; -#if WINDOWS - this.timerThread.Priority = ThreadPriority.Highest; -#endif - this.timerThread.IsBackground = true; - this.timerThread.Start(); - } - - /// Immediately releases all resources owned by the instance - public void Dispose() { - if(this.timerThread != null) { - this.endRequested = true; - this.notificationWaitEvent.Set(); - - // Wait for the timer thread to exit. If it doesn't exit in 10 seconds (which is - // a lot of time given that it doesn't do any real work), forcefully abort - // the thread. This may risk some leaks, but it's the only thing we can do. - bool success = this.timerThread.Join(2500); -#if WINDOWS - Trace.Assert(success, "Scheduler timer thread did not exit in time"); -#endif - // Unsubscribe from the time source to avoid surprise events during or - // after shutdown - if(this.timeSource != null) { - this.timeSource.DateTimeAdjusted -= this.dateTimeAdjustedDelegate; - this.timeSource = null; - } - - // Get rid of the notification wait event now that we've made sure that - // the timer thread is down. - this.notificationWaitEvent.Close(); - - // Help the GC a bit - this.notificationWaitEvent = null; - this.notifications = null; - - // Set to null so we don't attempt to end the thread again if Dispose() is - // called multiple times. - this.timerThread = null; - } - } - - /// Time source being used by the scheduler - public ITimeSource TimeSource { - get { return this.timeSource; } - } - - /// Schedules a notification at the specified absolute time - /// - /// Absolute time at which the notification will occur - /// - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - /// - /// The notification is scheduled for the indicated absolute time. If the system - /// enters/leaves daylight saving time or the date/time is changed (for example - /// when the system synchronizes with an NTP server), this will affect - /// the notification. So if you need to be notified after a fixed time, use - /// the NotifyIn() method instead. - /// - public object NotifyAt(DateTime notificationTime, WaitCallback callback) { - if(notificationTime.Kind == DateTimeKind.Unspecified) { - throw new ArgumentException( - "Notification time is neither UTC or local", "notificationTime" - ); - } - - DateTime notificationTimeUtc = notificationTime.ToUniversalTime(); - DateTime now = this.timeSource.CurrentUtcTime; - - long remainingTicks = notificationTimeUtc.Ticks - now.Ticks; - long nextDueTicks = this.timeSource.Ticks + remainingTicks; - - return scheduleNotification( - new Notification( - 0, - nextDueTicks, - notificationTimeUtc, - callback - ) - ); - } - - /// Schedules a notification after the specified time span - /// Delay after which the notification will occur - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - public object NotifyIn(TimeSpan delay, WaitCallback callback) { - return scheduleNotification( - new Notification( - 0, - this.timeSource.Ticks + delay.Ticks, - DateTime.MinValue, - callback - ) - ); - } - - /// - /// Schedules a notification after the specified amount of milliseconds - /// - /// - /// Number of milliseconds after which the notification will occur - /// - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - public object NotifyIn(int delayMilliseconds, WaitCallback callback) { - return scheduleNotification( - new Notification( - 0, - this.timeSource.Ticks + ((long)delayMilliseconds * TicksPerMillisecond), - DateTime.MinValue, - callback - ) - ); - } - - /// - /// Schedules a recurring notification after the specified time span - /// - /// Delay after which the first notification will occur - /// Interval at which the notification will be repeated - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - public object NotifyEach(TimeSpan delay, TimeSpan interval, WaitCallback callback) { - return scheduleNotification( - new Notification( - interval.Ticks, - this.timeSource.Ticks + delay.Ticks, - DateTime.MinValue, - callback - ) - ); - } - - /// - /// Schedules a recurring notification after the specified amount of milliseconds - /// - /// - /// Milliseconds after which the first notification will occur - /// - /// - /// Interval in milliseconds at which the notification will be repeated - /// - /// - /// Callback that will be invoked when the notification is due - /// - /// A handle that can be used to cancel the notification - public object NotifyEach( - int delayMilliseconds, int intervalMilliseconds, WaitCallback callback - ) { - return scheduleNotification( - new Notification( - (long)intervalMilliseconds * TicksPerMillisecond, - this.timeSource.Ticks + ((long)delayMilliseconds * TicksPerMillisecond), - DateTime.MinValue, - callback - ) - ); - } - - /// Cancels a scheduled notification - /// - /// Handle of the notification that will be cancelled - /// - public void Cancel(object notificationHandle) { - Notification notification = notificationHandle as Notification; - if(notification != null) { - notification.Cancelled = true; - } - } - - /// Called when the system date/time have been adjusted - /// Time source which detected the adjustment - /// Not used - private void dateTimeAdjusted(object sender, EventArgs arguments) { - lock(this.timerThread) { - long currentTicks = this.timeSource.Ticks; - DateTime currentTime = this.timeSource.CurrentUtcTime; - - PriorityQueue updatedQueue = new PriorityQueue( - NotificationComparer.Default - ); - - // Copy all notifications from the original queue to a new one, adjusting - // those with an absolute notification time along the way to a new due tick - while(this.notifications.Count > 0) { - Notification notification = this.notifications.Dequeue(); - if(!notification.Cancelled) { - - // If this notification has an absolute due time, adjust its due tick - if(notification.AbsoluteUtcTime != DateTime.MinValue) { - - // Combining recurrent notifications with absolute time isn't allowed - Debug.Assert(notification.IntervalTicks == 0); - - // Make the adjustment - long remainingTicks = (notification.AbsoluteUtcTime - currentTime).Ticks; - notification.NextDueTicks = currentTicks + remainingTicks; - - } - - // Notification processed, move it over to the new priority queue - updatedQueue.Enqueue(notification); - - } - } - - // Replace the working queue with the updated queue - this.notifications = updatedQueue; - } - - this.notificationWaitEvent.Set(); - } - - /// Schedules a notification for processing by the timer thread - /// Notification that will be scheduled - /// The scheduled notification - private object scheduleNotification(Notification notification) { - lock(this.timerThread) { - this.notifications.Enqueue(notification); - - // If this notification has become the next due notification, wake up - // the timer thread so it can adjust its sleep period. - if(ReferenceEquals(this.notifications.Peek(), notification)) { - this.notificationWaitEvent.Set(); - } - } - - return notification; - } - - /// Executes the timer thread - private void runTimerThread() { - Notification nextDueNotification; - lock(this.timerThread) { - nextDueNotification = getNextDueNotification(); - } - - // Keep processing notifications until we're told to quit - for(; ; ) { - - // Wait until the nextmost notification is due or something else wakes us up - if(nextDueNotification == null) { - this.notificationWaitEvent.WaitOne(); - } else { - long remainingTicks = nextDueNotification.NextDueTicks - this.timeSource.Ticks; - if(remainingTicks > 0) { - this.timeSource.WaitOne(this.notificationWaitEvent, remainingTicks); - } - } - - // Have we been woken up because the Scheduler is being disposed? - if(this.endRequested) { - break; - } - - // Process all notifications that are due by handing them over to the thread pool. - // The notification queue might have been updated while we were sleeping, so - // look for the notification that is due next again - long ticks = this.timeSource.Ticks; - lock(this.timerThread) { - for(; ; ) { - nextDueNotification = getNextDueNotification(); - if(nextDueNotification == null) { - break; - } - - // If the next notification is more than a millisecond away, we've reached - // the end of the notifications we need to process. - long remainingTicks = (nextDueNotification.NextDueTicks - ticks); - if(remainingTicks >= TicksPerMillisecond) { - break; - } - - if(!nextDueNotification.Cancelled) { - ThreadPool.QueueUserWorkItem(nextDueNotification.Callback); - } - - this.notifications.Dequeue(); - if(nextDueNotification.IntervalTicks != 0) { - nextDueNotification.NextDueTicks += nextDueNotification.IntervalTicks; - this.notifications.Enqueue(nextDueNotification); - } - } // for - } // lock - - } // for - } - - /// Retrieves the notification that is due next - /// The notification that is due next - private Notification getNextDueNotification() { - while(this.notifications.Count > 0) { - Notification nextDueNotification = this.notifications.Peek(); - if(nextDueNotification.Cancelled) { - this.notifications.Dequeue(); - } else { - return nextDueNotification; - } - } - - return null; - } - - /// Time source used by the scheduler - private ITimeSource timeSource; - /// Thread that will wait for the next scheduled event - private Thread timerThread; - /// Notifications in the scheduler's queue - private PriorityQueue notifications; - - /// Event used by the timer thread to wait for the next notification - private AutoResetEvent notificationWaitEvent; - /// Whether the timer thread should end - private volatile bool endRequested; - - /// Delegate for the dateTimeAdjusted() method - private EventHandler dateTimeAdjustedDelegate; - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/ThreadCallbackOperation.Test.cs b/Source/Scheduling/ThreadCallbackOperation.Test.cs deleted file mode 100644 index 44ecf1e..0000000 --- a/Source/Scheduling/ThreadCallbackOperation.Test.cs +++ /dev/null @@ -1,153 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; -using System.Threading; - -using NUnit.Framework; - -namespace Nuclex.Support.Scheduling { - - /// Unit Test for the thread callback operation class - [TestFixture] - public class ThreadCallbackOperationTest { - - #region class TestThreadOperation - - /// - /// Provides a test callback for unit testing the thread callback operation - /// - private class TestCallbackProvider { - - /// Method that can be invoked as a callback - public void Callback() { - this.called = true; - } - - /// Whether the callback has been called - public bool Called { - get { return this.called; } - } - - /// Set to true when the callback has been called - private bool called; - - } - - #endregion // class TestOperation - - /// Verifies that the default constructor for a thread operation works - [Test] - public void TestDefaultConstructor() { - ThreadCallbackOperation test = new ThreadCallbackOperation( - new ThreadStart(errorCallback) - ); - Assert.IsFalse(test.Ended); - } - - /// - /// Verifies that the threaded operation can execute in a thread pool thread - /// - [Test] - public void TestExecutionInThreadPool() { - TestCallbackProvider callbackProvider = new TestCallbackProvider(); - - ThreadCallbackOperation test = new ThreadCallbackOperation( - new ThreadStart(callbackProvider.Callback), true - ); - - Assert.IsFalse(test.Ended); - Assert.IsFalse(callbackProvider.Called); - test.Start(); - test.Join(); - Assert.IsTrue(test.Ended); - Assert.IsTrue(callbackProvider.Called); - } - - /// - /// Verifies that the threaded operation can execute in an explicit thread - /// - [Test] - public void TestExecutionInExplicitThread() { - TestCallbackProvider callbackProvider = new TestCallbackProvider(); - - ThreadCallbackOperation test = new ThreadCallbackOperation( - new ThreadStart(callbackProvider.Callback), false - ); - - Assert.IsFalse(test.Ended); - Assert.IsFalse(callbackProvider.Called); - test.Start(); - test.Join(); - Assert.IsTrue(test.Ended); - Assert.IsTrue(callbackProvider.Called); - } - - /// - /// Verifies that the threaded operation forwards an exception that occurred in - /// a thread pool thread. - /// - [Test] - public void TestForwardExceptionFromThreadPool() { - ThreadCallbackOperation test = new ThreadCallbackOperation( - new ThreadStart(errorCallback), false - ); - - Assert.IsFalse(test.Ended); - test.Start(); - Assert.Throws( - delegate() { test.Join(); } - ); - } - - /// - /// Verifies that the threaded operation forwards an exception that occurred in - /// an explicit thread. - /// - [Test] - public void TestForwardExceptionFromExplicitThread() { - ThreadCallbackOperation test = new ThreadCallbackOperation( - new ThreadStart(errorCallback), false - ); - - Assert.IsFalse(test.Ended); - test.Start(); - Assert.Throws( - delegate() { test.Join(); } - ); - } - - /// - /// Callback which throws an exception to simulate an error during callback execution - /// - private static void errorCallback() { - throw new AbortedException("Hello World"); - } - - } - -} // namespace Nuclex.Support.Scheduling - -#endif // UNITTEST diff --git a/Source/Scheduling/ThreadCallbackOperation.cs b/Source/Scheduling/ThreadCallbackOperation.cs deleted file mode 100644 index 15fc980..0000000 --- a/Source/Scheduling/ThreadCallbackOperation.cs +++ /dev/null @@ -1,69 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Nuclex.Support.Scheduling { - - /// Operation that executes a method in a background thread - public class ThreadCallbackOperation : ThreadOperation { - - /// - /// Initializes a new threaded method operation that will call back a - /// parameterless method from the background thread. - /// - /// Method to be invoked in a background thread - /// - /// Uses a ThreadPool thread to execute the method in - /// - public ThreadCallbackOperation(ThreadStart method) - : this(method, true) { } - - /// - /// Initializes a new threaded method operation that will call back a - /// parameterless method from the background thread and use the - /// thread pool optionally. - /// - /// Method to be invoked in a background thread - /// Whether to use a ThreadPool thread - /// - /// If useThreadPool is false, a new thread will be created. This guarantees - /// that the method will be executed immediately but has an impact on - /// performance since the creation of new threads is not a cheap operation. - /// - public ThreadCallbackOperation(ThreadStart method, bool useThreadPool) - : base(useThreadPool) { - - this.method = method; - } - - /// Executes the thread callback in the background thread - protected override void Execute() { - this.method(); - } - - /// Method to be invoked in a background thread - private ThreadStart method; - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/ThreadOperation.Test.cs b/Source/Scheduling/ThreadOperation.Test.cs deleted file mode 100644 index 0558509..0000000 --- a/Source/Scheduling/ThreadOperation.Test.cs +++ /dev/null @@ -1,139 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; - - -using NUnit.Framework; - -namespace Nuclex.Support.Scheduling { - - /// Unit Test for the thread operation class - [TestFixture] - public class ThreadOperationTest { - - #region class TestThreadOperation - - /// Dummy operation used to run the unit tests - private class TestThreadOperation : ThreadOperation { - - /// Initializes a dummy operation - public TestThreadOperation() { } - - /// Initializes a dummy operation - /// Whether to use a ThreadPool thread. - public TestThreadOperation(bool useThreadPool) : base(useThreadPool) { } - - /// Contains the payload to be executed in the background thread - protected override void Execute() { } - - } - - #endregion // class TestOperation - - #region class FailingThreadOperation - - /// Dummy operation used to run the unit tests - private class FailingThreadOperation : ThreadOperation { - - /// Initializes a dummy operation - /// Whether to use a ThreadPool thread. - public FailingThreadOperation(bool useThreadPool) : base(useThreadPool) { } - - /// Contains the payload to be executed in the background thread - protected override void Execute() { - throw new AbortedException("Hello World"); - } - - } - - #endregion // class FailingThreadOperation - - /// Verifies that the default constructor for a thread operation works - [Test] - public void TestDefaultConstructor() { - TestThreadOperation test = new TestThreadOperation(); - Assert.IsFalse(test.Ended); - } - - /// - /// Verifies that the threaded operation can execute in a thread pool thread - /// - [Test] - public void TestExecutionInThreadPool() { - TestThreadOperation test = new TestThreadOperation(true); - Assert.IsFalse(test.Ended); - test.Start(); - test.Join(); - Assert.IsTrue(test.Ended); - } - - /// - /// Verifies that the threaded operation can execute in an explicit thread - /// - [Test] - public void TestExecutionInExplicitThread() { - TestThreadOperation test = new TestThreadOperation(false); - Assert.IsFalse(test.Ended); - test.Start(); - test.Join(); - Assert.IsTrue(test.Ended); - } - - /// - /// Verifies that the threaded operation forwards an exception that occurred in - /// a thread pool thread. - /// - [Test] - public void TestForwardExceptionFromThreadPool() { - FailingThreadOperation test = new FailingThreadOperation(true); - - Assert.IsFalse(test.Ended); - test.Start(); - Assert.Throws( - delegate() { test.Join(); } - ); - } - - /// - /// Verifies that the threaded operation forwards an exception that occurred in - /// an explicit thread. - /// - [Test] - public void TestForwardExceptionFromExplicitThread() { - FailingThreadOperation test = new FailingThreadOperation(false); - - Assert.IsFalse(test.Ended); - test.Start(); - Assert.Throws( - delegate() { test.Join(); } - ); - } - - } - -} // namespace Nuclex.Support.Scheduling - -#endif // UNITTEST diff --git a/Source/Scheduling/ThreadOperation.cs b/Source/Scheduling/ThreadOperation.cs deleted file mode 100644 index 67351b6..0000000 --- a/Source/Scheduling/ThreadOperation.cs +++ /dev/null @@ -1,112 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; - -namespace Nuclex.Support.Scheduling { - - /// Operation that executes a method in a background thread - public abstract class ThreadOperation : Operation { - - /// - /// Initializes a new threaded operation. - /// - /// - /// Uses a ThreadPool thread to execute the method in a background thread. - /// - public ThreadOperation() : this(true) { } - - /// - /// Initializes a new threaded operation which optionally uses the ThreadPool. - /// - /// Whether to use a ThreadPool thread. - /// - /// If useThreadPool is false, a new thread will be created. This guarantees - /// that the method will be executed immediately but has an impact on - /// performance since the creation of new threads is not a cheap operation. - /// - public ThreadOperation(bool useThreadPool) { - this.useThreadPool = useThreadPool; - } - - /// Launches the background operation - public override void Start() { - Debug.Assert( - !Ended, - "Tried to Start an Operation again that has already ended", - "Operations cannot be re-run" - ); - if(useThreadPool) { - ThreadPool.QueueUserWorkItem(new WaitCallback(callMethod)); - } else { - Thread thread = new Thread(new ThreadStart(callMethod)); - thread.Name = "Nuclex.Support.Scheduling.ThreadOperation"; - thread.IsBackground = true; - thread.Start(); - } - } - - /// Contains the payload to be executed in the background thread - protected abstract void Execute(); - - /// Invokes the delegate passed as an argument - /// Not used - private void callMethod(object state) { - callMethod(); - } - - /// Invokes the delegate passed as an argument - private void callMethod() { - try { - Execute(); - Debug.Assert( - !Ended, - "Operation unexpectedly ended during Execute()", - "Do not call OnAsyncEnded() yourself when deriving from ThreadOperation" - ); - } - catch(Exception exception) { - this.exception = exception; - } - finally { - OnAsyncEnded(); - } - } - - /// - /// Allows the specific request implementation to re-throw an exception if - /// the background process finished unsuccessfully - /// - protected override void ReraiseExceptions() { - if(this.exception != null) - throw this.exception; - } - - /// Whether to use the ThreadPool for obtaining a background thread - private bool useThreadPool; - /// Exception that has occured in the background process - private volatile Exception exception; - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/WindowsTimeSource.Test.cs b/Source/Scheduling/WindowsTimeSource.Test.cs deleted file mode 100644 index 7ef272e..0000000 --- a/Source/Scheduling/WindowsTimeSource.Test.cs +++ /dev/null @@ -1,164 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -#if WINDOWS - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading; - -using Microsoft.Win32; - -using NUnit.Framework; - -namespace Nuclex.Support.Scheduling { - - /// Unit Test for the windows time source - [TestFixture] - public class WindowsTimeSourceTest { - - #region class TestWindowsTimeSource - - /// Windows time source used for testing - private class TestWindowsTimeSource : WindowsTimeSource { - - /// - /// Forces a time change notification even if the system time was not adjusted - /// - public void ForceTimeChange() { - OnDateTimeAdjusted(this, EventArgs.Empty); - } - } - - #endregion // class TestWindowsTimeSource - - #region class TestTimeChangedSubscriber - - /// Dummy subscriber used to test the time changed event - private class TestTimeChangedSubscriber { - - /// Callback subscribed to the TimeChanged event - /// Not used - /// Not used - public void TimeChanged(object sender, EventArgs arguments) { - ++this.CallCount; - } - - /// Number of times the callback was invoked - public int CallCount; - - } - - #endregion // class TestTimeChangedSubscriber - - /// - /// Verifies that the time source's default constructor is working - /// - [Test] - public void TestDefaultConstructor() { - using(WindowsTimeSource timeSource = new WindowsTimeSource()) { } - } - - /// - /// Verifies that the time source can provide the current UTC time - /// - [Test] - public void TestCurrentUtcTime() { - using(WindowsTimeSource timeSource = new WindowsTimeSource()) { - - Assert.That( - timeSource.CurrentUtcTime, Is.EqualTo(DateTime.UtcNow).Within(10).Seconds - ); - } - } - - /// - /// Verifies that the time source's tick property is working if - /// the Stopwatch class is used to measure time - /// - [Test] - public void TestTicks() { - using(WindowsTimeSource timeSource = new WindowsTimeSource()) { - long ticks1 = timeSource.Ticks; - long ticks2 = timeSource.Ticks; - - Assert.That(ticks2, Is.GreaterThanOrEqualTo(ticks1)); - } - } - - /// - /// Verifies that the time source's WaitOne() method works correctly - /// - [Test] - public void TestWaitOne() { - using(WindowsTimeSource timeSource = new WindowsTimeSource()) { - AutoResetEvent waitEvent = new AutoResetEvent(true); - - Assert.IsTrue(timeSource.WaitOne(waitEvent, TimeSpan.FromMilliseconds(1).Ticks)); - Assert.IsFalse(timeSource.WaitOne(waitEvent, TimeSpan.FromMilliseconds(1).Ticks)); - } - } - - /// - /// Verifies that the default time source's WaitOne() method works correctly - /// - [Test] - public void TestTimeChange() { - using(TestWindowsTimeSource timeSource = new TestWindowsTimeSource()) { - TestTimeChangedSubscriber subscriber = new TestTimeChangedSubscriber(); - - EventHandler callbackDelegate = new EventHandler(subscriber.TimeChanged); - timeSource.DateTimeAdjusted += callbackDelegate; - try { - timeSource.ForceTimeChange(); - } - finally { - timeSource.DateTimeAdjusted -= callbackDelegate; - } - - // Using greater than because during the test run a real time change notification - // might have happened, increasing the counter to 2 or more. - Assert.That(subscriber.CallCount, Is.GreaterThanOrEqualTo(1)); - } - } - - /// - /// Tests whether the Windows-specific time source can reports its availability on - /// the current platform - /// - [Test] - public void TestAvailability() { - bool isAvailable = WindowsTimeSource.Available; - Assert.IsTrue( - (isAvailable == true) || - (isAvailable == false) - ); - } - - } - -} // namespace Nuclex.Support.Scheduling - -#endif // WINDOWS - -#endif // UNITTEST diff --git a/Source/Scheduling/WindowsTimeSource.cs b/Source/Scheduling/WindowsTimeSource.cs deleted file mode 100644 index e40b5c1..0000000 --- a/Source/Scheduling/WindowsTimeSource.cs +++ /dev/null @@ -1,91 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Threading; - -#if !NO_SYSTEMEVENTS -using Microsoft.Win32; -#endif - -namespace Nuclex.Support.Scheduling { - - /// - /// Time source that makes use of additional features only available on Windows - /// - public class WindowsTimeSource : GenericTimeSource, IDisposable { - - /// Number of ticks (100 ns intervals) in a millisecond - private const long TicksPerMillisecond = 10000; - - /// Initializes a new Windows time source - public WindowsTimeSource() { -#if NO_SYSTEMEVENTS - throw new InvalidOperationException( - "Windows time source is not available without the SystemEvents class" - ); -#else - this.onDateTimeAdjustedDelegate = new EventHandler(OnDateTimeAdjusted); - SystemEvents.TimeChanged += this.onDateTimeAdjustedDelegate; -#endif - } - - /// Immediately releases all resources owned by the instance - public void Dispose() { -#if !NO_SYSTEMEVENTS - if (this.onDateTimeAdjustedDelegate != null) { - SystemEvents.TimeChanged -= this.onDateTimeAdjustedDelegate; - this.onDateTimeAdjustedDelegate = null; - } -#endif - } - - /// Waits for an AutoResetEvent to become signalled - /// WaitHandle the method will wait for - /// Number of ticks to wait - /// - /// True if the WaitHandle was signalled, false if the timeout was reached - /// - public override bool WaitOne(AutoResetEvent waitHandle, long ticks) { -#if WINDOWS - return waitHandle.WaitOne((int)(ticks / TicksPerMillisecond), false); -#else - return waitHandle.WaitOne((int)(ticks / TicksPerMillisecond)); -#endif - } - - /// - /// Whether the Windows time source can be used on the current platform - /// - public static bool Available { - get { return Environment.OSVersion.Platform == PlatformID.Win32NT; } - } - -#if !NO_SYSTEMEVENTS - - /// Delegate for the timeChanged() callback method - private EventHandler onDateTimeAdjustedDelegate; - -#endif // !NO_SYSTEMEVENTS - - } - -} // namespace Nuclex.Support.Scheduling diff --git a/Source/Tracking/IProgressReporter.cs b/Source/Tracking/IProgressReporter.cs deleted file mode 100644 index f9523e4..0000000 --- a/Source/Tracking/IProgressReporter.cs +++ /dev/null @@ -1,34 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Tracking { - - /// Interface for processes that report their progress - public interface IProgressReporter { - - /// Triggered when the status of the process changes - event EventHandler AsyncProgressChanged; - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/IStatusReporter.cs b/Source/Tracking/IStatusReporter.cs deleted file mode 100644 index 91bd1cc..0000000 --- a/Source/Tracking/IStatusReporter.cs +++ /dev/null @@ -1,34 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Tracking { - - /// Interface for processes that report their status - public interface IStatusReporter { - - /// Triggered when the status of the process changes - event EventHandler AsyncStatusChanged; - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/IdleStateEventArgs.Test.cs b/Source/Tracking/IdleStateEventArgs.Test.cs deleted file mode 100644 index 6eefc8c..0000000 --- a/Source/Tracking/IdleStateEventArgs.Test.cs +++ /dev/null @@ -1,58 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.IO; - -using NUnit.Framework; - -namespace Nuclex.Support.Tracking { - - /// Unit Test for the "idle state" event argument container - [TestFixture] - public class IdleStateEventArgsTest { - - /// - /// Tests whether the idle state event arguments correctly report a non-idle state - /// - [Test] - public void TestIdleStateChangedToFalse() { - IdleStateEventArgs idleStateFalse = new IdleStateEventArgs(false); - - Assert.IsFalse(idleStateFalse.Idle); - } - - /// - /// Tests whether the idle state event arguments correctly report an idle state - /// - [Test] - public void TestIdleStateChangedToTrue() { - IdleStateEventArgs idleStateFalse = new IdleStateEventArgs(true); - - Assert.IsTrue(idleStateFalse.Idle); - } - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/IdleStateEventArgs.cs b/Source/Tracking/IdleStateEventArgs.cs deleted file mode 100644 index 1143f74..0000000 --- a/Source/Tracking/IdleStateEventArgs.cs +++ /dev/null @@ -1,45 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Tracking { - - /// Event arguments for an idle state change notification - public class IdleStateEventArgs : EventArgs { - - /// Initializes the idle state change notification - /// The new idle state - public IdleStateEventArgs(bool idle) { - this.idle = idle; - } - - /// Current idle state - public bool Idle { - get { return this.idle; } - } - - /// Current idle state - private bool idle; - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/Internal/ObservedWeightedTransaction.Test.cs b/Source/Tracking/Internal/ObservedWeightedTransaction.Test.cs deleted file mode 100644 index 338ef2b..0000000 --- a/Source/Tracking/Internal/ObservedWeightedTransaction.Test.cs +++ /dev/null @@ -1,160 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.IO; -using System.Threading; - -#if UNITTEST - -using NUnit.Framework; -using NMock; - -namespace Nuclex.Support.Tracking { - - /// Unit Test for the observation wrapper of weighted transactions - [TestFixture] - public class ObservedWeightedTransactionTest { - - #region interface IObservationSubscriber - - /// - /// Interface used to test the observation wrapper of weighted transactions - /// - public interface IObservationSubscriber { - - /// Will be invoked when an observed transaction's progress changes - void ProgressUpdated(); - - /// Will be invoked when an observed transaction completes - void Ended(); - - } - - #endregion // interface IObservationSubscriber - - #region class FunkyTransaction - - /// - /// Transaction that goes into the 'ended' state as soon as someone registers for - /// state change notifications - /// - private class FunkyTransaction : Transaction { - - /// Manages registrations to the AsyncEnded event - public override event EventHandler AsyncEnded { - add { - base.AsyncEnded += value; - - // To deterministically provoke an 'Ended' event just after registration we - // will switch the transaction into the 'ended' state right here - int oldValue = Interlocked.Exchange(ref this.alreadyEnded, 1); - if(oldValue != 1) { - OnAsyncEnded(); - } - } - remove { - base.AsyncEnded -= value; - } - } - - /// Whether the transaction has already been ended - private int alreadyEnded; - - } - - #endregion // class FunkyTransaction - - /// Initialization routine executed before each test is run - [SetUp] - public void Setup() { - this.mockery = new MockFactory(); - } - - /// Verifies that the constructor of the observation wrapper works - [Test] - public void TestConstructorWithAlreadyEndedTransaction() { - WeightedTransaction testTransaction = new WeightedTransaction( - Transaction.EndedDummy - ); - - Mock subscriber = this.mockery.CreateMock(); - - subscriber.Expects.AtLeast(0).Method(m => m.ProgressUpdated()); - // This should no be called because otherwise, the 'Ended' event would be raised - // to the transaction group before all transactions have been added into - // the internal list, leading to an early ending or even multiple endings. - subscriber.Expects.No.Method(m => m.Ended()); - - using( - ObservedWeightedTransaction test = - new ObservedWeightedTransaction( - testTransaction, - new ObservedWeightedTransaction.ReportDelegate( - subscriber.MockObject.ProgressUpdated - ), - new ObservedWeightedTransaction.ReportDelegate( - subscriber.MockObject.Ended - ) - ) - ) { - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - } - - /// - /// Verifies that the constructor of the observation wrapper can handle a transaction - /// entering the 'ended' state right on subscription - /// - [Test] - public void TestConstructorWithEndingTransaction() { - WeightedTransaction testTransaction = new WeightedTransaction( - new FunkyTransaction() - ); - - Mock subscriber = this.mockery.CreateMock(); - - subscriber.Expects.AtLeast(0).Method(m => m.ProgressUpdated()); - subscriber.Expects.One.Method(m => m.Ended()); - - using( - ObservedWeightedTransaction test = - new ObservedWeightedTransaction( - testTransaction, - new ObservedWeightedTransaction.ReportDelegate( - subscriber.MockObject.ProgressUpdated - ), - new ObservedWeightedTransaction.ReportDelegate( - subscriber.MockObject.Ended - ) - ) - ) { - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - } - - /// Mock object factory - private MockFactory mockery; - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/Internal/ObservedWeightedTransaction.cs b/Source/Tracking/Internal/ObservedWeightedTransaction.cs deleted file mode 100644 index e4259b1..0000000 --- a/Source/Tracking/Internal/ObservedWeightedTransaction.cs +++ /dev/null @@ -1,186 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Tracking { - - /// Transaction being observed by another object - /// - /// Type of the transaction that is being observed - /// - internal class ObservedWeightedTransaction : IDisposable - where TransactionType : Transaction { - - /// Delegate for reporting progress updates - public delegate void ReportDelegate(); - - /// Initializes a new observed transaction - /// Weighted transaction being observed - /// - /// Callback to invoke when the transaction's progress changes - /// - /// - /// Callback to invoke when the transaction has ended - /// - internal ObservedWeightedTransaction( - WeightedTransaction weightedTransaction, - ReportDelegate progressUpdateCallback, - ReportDelegate endedCallback - ) { - this.weightedTransaction = weightedTransaction; - - // See if this transaction has already ended (initial check for performance) - if(weightedTransaction.Transaction.Ended) { - - // Since we don't subscribe to the .Ended event (which would be fired immediately on - // subscription if the transaction was already finished), we will emulate this - // behavior here. There is no race condition here: The transition to .Ended occurs - // only once and will never happen in reverse. This is just a minor optimization to - // prevent object coupling where none is neccessary and to save some processing time. - this.progress = 1.0f; - progressUpdateCallback(); - - // Do not call the ended callback here. This constructor is called when the - // TransactionGroup constructs its list of transactions. If this is called and - // the first transaction to be added to the group happens to be in the ended - // state, the transactionGroup will immediately think it has ended! - //!DONT!endedCallback(); - - return; - - } - - this.endedCallback = endedCallback; - this.progressUpdateCallback = progressUpdateCallback; - - // This might trigger the event handler to be invoked right here if the transaction - // ended between our initial optimization attempt and this line. It's unlikely, - // however, so we'll not waste time with another optimization attempt. - this.weightedTransaction.Transaction.AsyncEnded += new EventHandler(asyncEnded); - - // See whether this transaction implements the IProgressReporter interface and if - // so, connect to its progress report event in order to pass these reports on - // to whomever created ourselfes. - this.progressReporter = this.weightedTransaction.Transaction as IProgressReporter; - if(this.progressReporter != null) { - this.asyncProgressChangedEventHandler = new EventHandler( - asyncProgressChanged - ); - this.progressReporter.AsyncProgressChanged += this.asyncProgressChangedEventHandler; - } - } - - /// Immediately releases all resources owned by the object - public void Dispose() { - asyncDisconnectEvents(); - } - - /// Weighted transaction being observed - public WeightedTransaction WeightedTransaction { - get { return this.weightedTransaction; } - } - - /// Amount of progress this transaction has achieved so far - public float Progress { - get { return this.progress; } - } - - /// Called when the observed transaction has ended - /// Transaction that has ended - /// Not used - private void asyncEnded(object sender, EventArgs e) { - ReportDelegate savedEndedCallback = this.endedCallback; - ReportDelegate savedProgressUpdateCallback = this.progressUpdateCallback; - - asyncDisconnectEvents(); // We don't need those anymore! - - // If the progress hasn't reached 1.0 yet, make a fake report so that even - // when a transaction doesn't report any progress at all, the set or queue - // owning us will have a percentage of transactions completed. - // - // There is the possibility of a race condition here, as a final progress - // report could have been generated by a thread running the transaction - // that was preempted by this thread. This would cause the progress to - // jump to 1.0 and then back to whatever the waiting thread will report. - if(this.progress != 1.0f) { - this.progress = 1.0f; - savedProgressUpdateCallback(); - } - - savedEndedCallback(); - } - - /// Called when the progress of the observed transaction changes - /// Transaction whose progress has changed - /// Contains the updated progress - private void asyncProgressChanged(object sender, ProgressReportEventArgs arguments) { - this.progress = arguments.Progress; - - ReportDelegate savedProgressUpdateCallback = this.progressUpdateCallback; - if(savedProgressUpdateCallback != null) { - savedProgressUpdateCallback(); - } - } - - /// Unsubscribes from all events of the observed transaction - private void asyncDisconnectEvents() { - - // Make use of the double check locking idiom to avoid the costly lock when - // the events have already been unsubscribed - if(this.endedCallback != null) { - - // This is an internal class with special knowledge that there - // is no risk of deadlock involved, so we don't need a fancy syncRoot! - lock(this) { - if(this.endedCallback != null) { - this.weightedTransaction.Transaction.AsyncEnded -= new EventHandler(asyncEnded); - - if(this.progressReporter != null) { - this.progressReporter.AsyncProgressChanged -= - this.asyncProgressChangedEventHandler; - - this.asyncProgressChangedEventHandler = null; - } - - this.endedCallback = null; - this.progressUpdateCallback = null; - } - } - - } // endedCallback != null - - } - - private EventHandler asyncProgressChangedEventHandler; - /// The observed transaction's progress reporting interface - private IProgressReporter progressReporter; - /// The weighted wable that is being observed - private WeightedTransaction weightedTransaction; - /// Callback to invoke when the progress updates - private volatile ReportDelegate progressUpdateCallback; - /// Callback to invoke when the transaction ends - private volatile ReportDelegate endedCallback; - /// Progress achieved so far - private volatile float progress; - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/Internal/WeightedTransactionWrapperCollection.Test.cs b/Source/Tracking/Internal/WeightedTransactionWrapperCollection.Test.cs deleted file mode 100644 index 30e441b..0000000 --- a/Source/Tracking/Internal/WeightedTransactionWrapperCollection.Test.cs +++ /dev/null @@ -1,71 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.IO; -using System.Threading; - -#if UNITTEST - -using NUnit.Framework; - -namespace Nuclex.Support.Tracking { - - /// - /// Unit Test for the observation wrapper collection of weighted transactions - /// - [TestFixture] - public class WeightedTransactionWrapperCollectionTest { - - /// - /// Tests whether the wrapper collection is handing out the unwrapped transactions - /// - [Test] - public void TestWrapperCollection() { - WeightedTransaction transaction = new WeightedTransaction( - Transaction.EndedDummy - ); - - ObservedWeightedTransaction observed = - new ObservedWeightedTransaction( - transaction, - endedCallback, - progressUpdatedCallback - ); - - WeightedTransactionWrapperCollection wrapper = - new WeightedTransactionWrapperCollection( - new ObservedWeightedTransaction[] { observed } - ); - - Assert.AreSame(transaction, wrapper[0]); - } - - /// Dummy callback used as event subscriber in the tests - private void endedCallback() { } - - /// Dummy callback used as event subscriber in the tests - private void progressUpdatedCallback() { } - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/Internal/WeightedTransactionWrapperCollection.cs b/Source/Tracking/Internal/WeightedTransactionWrapperCollection.cs deleted file mode 100644 index ab13b7c..0000000 --- a/Source/Tracking/Internal/WeightedTransactionWrapperCollection.cs +++ /dev/null @@ -1,80 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; - -using Nuclex.Support.Collections; - -namespace Nuclex.Support.Tracking { - - /// Collection of transactions with a weighting value - /// Type of transactions to manage - /// - /// - /// This collection is exposed as a read-only collection to the user that - /// stores WeightedTransactions. Internally, it merely wraps a collection of - /// an internal type used to keep track of the individual transaction's - /// progress in the TransactionGroup and OperationQueue classes. - /// - /// - /// It is read-only because the design requires a transaction to only ever finish - /// once. If it was possible eg. to add items after a TransactionGroup had signalled - /// itself as being finished, it would be moved into an unfinished state again. - /// Also, an empty TransactionGroup is, by definition, finished (simply because - /// there is no work to do) - unless the contents of the group are passed to the - /// TransactionGroup's constructor and never modified at all, the design would be - /// violated as soon as an instance of the TransactionGroup or OperationQueue - /// classes was created. - /// - /// - internal class WeightedTransactionWrapperCollection : - TransformingReadOnlyCollection< - ObservedWeightedTransaction, WeightedTransaction - > - where TransactionType : Transaction { - - /// Initializes a new weighted transaction collection wrapper - /// Items to be exposed as weighted transactions - internal WeightedTransactionWrapperCollection( - IList> items - ) - : base(items) { } - - /// Transforms an item into the exposed type - /// Item to be transformed - /// The transformed item - /// - /// This method is used to transform an item in the wrapped collection into - /// the exposed item type whenever the user accesses an item. Expect it to - /// be called frequently, because the TransformingReadOnlyCollection does - /// not cache otherwise store the transformed items. - /// - protected override WeightedTransaction Transform( - ObservedWeightedTransaction item - ) { - return item.WeightedTransaction; - } - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/ProgressReportEventArgs.Test.cs b/Source/Tracking/ProgressReportEventArgs.Test.cs deleted file mode 100644 index c3bf98d..0000000 --- a/Source/Tracking/ProgressReportEventArgs.Test.cs +++ /dev/null @@ -1,58 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.IO; - -using NUnit.Framework; - -namespace Nuclex.Support.Tracking { - - /// Unit Test for the progress report event argument container - [TestFixture] - public class ProgressReportEventArgsTest { - - /// - /// Tests whether the progress report event arguments correctly report zero progress - /// - [Test] - public void TestZeroProgress() { - ProgressReportEventArgs zeroProgress = new ProgressReportEventArgs(0.0f); - - Assert.AreEqual(0.0f, zeroProgress.Progress); - } - - /// - /// Tests whether the progress report event arguments correctly report complete progress - /// - [Test] - public void TestCompleteProgress() { - ProgressReportEventArgs zeroProgress = new ProgressReportEventArgs(1.0f); - - Assert.AreEqual(1.0f, zeroProgress.Progress); - } - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/ProgressReportEventArgs.cs b/Source/Tracking/ProgressReportEventArgs.cs deleted file mode 100644 index ea6fefb..0000000 --- a/Source/Tracking/ProgressReportEventArgs.cs +++ /dev/null @@ -1,45 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Tracking { - - /// Event arguments for a progress update notification - public class ProgressReportEventArgs : EventArgs { - - /// Initializes the progress update informations - /// Achieved progress ranging from 0.0 to 1.0 - public ProgressReportEventArgs(float progress) { - this.progress = progress; - } - - /// Currently achieved progress - public float Progress { - get { return this.progress; } - } - - /// Achieved progress - private float progress; - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/ProgressTracker.Test.cs b/Source/Tracking/ProgressTracker.Test.cs deleted file mode 100644 index 49d4be9..0000000 --- a/Source/Tracking/ProgressTracker.Test.cs +++ /dev/null @@ -1,509 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.IO; - -using NUnit.Framework; -using NMock; - -namespace Nuclex.Support.Tracking { - - /// Unit Test for the progress tracker class - [TestFixture] - public class ProgressTrackerTest { - - #region interface IProgressTrackerSubscriber - - /// Interface used to test the progress tracker - public interface IProgressTrackerSubscriber { - - /// Called when the tracked progress changes - /// Progress tracker whose progress has changed - /// Contains the new progress achieved - void ProgressChanged(object sender, ProgressReportEventArgs arguments); - - /// Called when the progress tracker's idle state changes - /// Progress tracker whose idle state has changed - /// Contains the new idle state of the tracker - void IdleStateChanged(object sender, IdleStateEventArgs arguments); - - } - - #endregion // interface IProgressTrackerSubscriber - - #region class ProgressUpdateEventArgsMatcher - - /// Compares two ProgressUpdateEventArgs instances - private class ProgressReportEventArgsMatcher : Matcher { - - /// Initializes a new ProgressUpdateEventArgsMatcher - /// Expected progress update event arguments - public ProgressReportEventArgsMatcher(ProgressReportEventArgs expected) { - this.expected = expected; - } - - /// - /// Called by NMock to verfiy the ProgressUpdateEventArgs match the expected value - /// - /// Actual value to compare to the expected value - /// - /// True if the actual value matches the expected value; otherwise false - /// - public override bool Matches(object actualAsObject) { - ProgressReportEventArgs actual = (actualAsObject as ProgressReportEventArgs); - if(actual == null) - return false; - - return (actual.Progress == this.expected.Progress); - } - - /// Creates a string representation of the expected value - /// Writer to write the string representation into - public override void DescribeTo(TextWriter writer) { - writer.Write(this.expected.Progress.ToString()); - } - - /// Expected progress update event args value - private ProgressReportEventArgs expected; - - } - - #endregion // class ProgressUpdateEventArgsMatcher - - #region class TestTransaction - - /// Transaction used for testing in this unit test - private class TestTransaction : Transaction, IProgressReporter { - - /// will be triggered to report when progress has been achieved - public event EventHandler AsyncProgressChanged; - - /// Changes the testing transaction's indicated progress - /// New progress to be reported by the testing transaction - public void ChangeProgress(float progress) { - OnAsyncProgressChanged(progress); - } - - /// Transitions the transaction into the ended state - public void End() { - OnAsyncEnded(); - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this transaction about the achieved progress. - /// - protected virtual void OnAsyncProgressChanged(float progress) { - OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this transaction about the achieved progress. - /// Allows for classes derived from the transaction class to easily provide - /// a custom event arguments class that has been derived from the - /// transaction's ProgressUpdateEventArgs class. - /// - protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { - EventHandler copy = AsyncProgressChanged; - if(copy != null) - copy(this, eventArguments); - } - - } - - #endregion // class TestTransaction - - #region class EvilTransaction - - /// - /// Transaction that tries to emulate a thread giving a progress report at - /// a very inconvenient time ;) - /// - private class EvilTransaction : Transaction, IProgressReporter { - - /// will be triggered to report when progress has been achieved - public event EventHandler AsyncProgressChanged { - add { } - remove { - // Send a progress update right when the subscriber is trying to unsubscribe - value(this, new ProgressReportEventArgs(0.5f)); - } - } - - } - - #endregion // class EvilTransaction - - /// Initialization routine executed before each test is run - [SetUp] - public void Setup() { - this.mockery = new MockFactory(); - } - - /// Validates that the tracker properly sums the progress - [Test] - public void TestSummedProgress() { - using(ProgressTracker tracker = new ProgressTracker()) { - Mock mockedSubscriber = mockSubscriber(tracker); - - TestTransaction test1 = new TestTransaction(); - TestTransaction test2 = new TestTransaction(); - - // Step 1 - { - mockedSubscriber.Expects.One.Method( - m => m.IdleStateChanged(null, null) - ).WithAnyArguments(); - - // Since the progress is already at 0, these redundant reports are optional - mockedSubscriber.Expects.Between(0, 2).Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(ProgressTracker)), - new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f)) - ); - - tracker.Track(test1); - tracker.Track(test2); - } - - // Step 2 - { - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(ProgressTracker)), - new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f)) - ); - - test1.ChangeProgress(0.5f); - } - } - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - - /// - /// Validates that the tracker only removes transactions when the whole - /// tracking list has reached the 'ended' state. - /// - /// - /// If the tracker would remove ended transactions right when they finished, - /// the total progress would jump back each time. This is unwanted, of course. - /// - [Test] - public void TestDelayedRemoval() { - using(ProgressTracker tracker = new ProgressTracker()) { - Mock mockedSubscriber = mockSubscriber(tracker); - - TestTransaction test1 = new TestTransaction(); - TestTransaction test2 = new TestTransaction(); - - // Step 1 - { - mockedSubscriber.Expects.One.Method( - m => m.IdleStateChanged(null, null) - ).WithAnyArguments(); - - // This is optional. The tracker's progress is currently 0, so there's no need - // to send out superfluous progress reports. - mockedSubscriber.Expects.Between(0, 2).Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(ProgressTracker)), - new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f)) - ); - - tracker.Track(test1); - tracker.Track(test2); - } - - // Step 2 - { - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(ProgressTracker)), - new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f)) - ); - - // Total progress should be 0.25 after this call (two transactions, one with - // 0% progress and one with 50% progress) - test1.ChangeProgress(0.5f); - } - - // Step 3 - { - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(ProgressTracker)), - new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.75f)) - ); - - // Total progress should be 0.75 after this call (one transaction at 100%, - // the other one at 50%). If the second transaction would be removed by the tracker, - // (which would be inappropriate!) the progress would falsely jump to 0.5 instead. - test2.End(); - } - - // Step 4 - { - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(ProgressTracker)), - new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f)) - ); - - mockedSubscriber.Expects.One.Method( - m => m.IdleStateChanged(null, null) - ).WithAnyArguments(); - - test1.End(); - } - } - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - - /// - /// Validates that the tracker behaves correctly if it is fed with transactions - /// that have already ended. - /// - [Test] - public void TestSoleEndedTransaction() { - using(ProgressTracker tracker = new ProgressTracker()) { - Mock mockedSubscriber = mockSubscriber(tracker); - - mockedSubscriber.Expects.No.Method( - m => m.IdleStateChanged(null, null) - ).WithAnyArguments(); - mockedSubscriber.Expects.No.Method( - m => m.ProgressChanged(null, null) - ).WithAnyArguments(); - - tracker.Track(Transaction.EndedDummy); - } - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - - /// - /// Validates that the tracker behaves correctly if it is fed with transactions - /// that have already ended in addition to transactions that are actively executing. - /// - [Test] - public void TestEndedTransaction() { - using(ProgressTracker tracker = new ProgressTracker()) { - Mock mockedSubscriber = mockSubscriber(tracker); - - TestTransaction test1 = new TestTransaction(); - - // Step 1 - { - mockedSubscriber.Expects.One.Method( - m => m.IdleStateChanged(null, null) - ).WithAnyArguments(); - - mockedSubscriber.Expects.AtMost(1).Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(ProgressTracker)), - new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f)) - ); - - tracker.Track(test1); - } - - // Step 2 - { - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(ProgressTracker)), - new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.5f)) - ); - - tracker.Track(Transaction.EndedDummy); - } - - // Step 3 - { - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(ProgressTracker)), - new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f)) - ); - - mockedSubscriber.Expects.One.Method( - m => m.IdleStateChanged(null, null) - ).WithAnyArguments(); - - test1.End(); - } - } - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - - /// - /// Tries to provoke a deadlock by re-entering the tracker from one of its own events - /// - [Test] - public void TestProvokedDeadlock() { - using(ProgressTracker tracker = new ProgressTracker()) { - TestTransaction test1 = new TestTransaction(); - tracker.Track(test1); - - tracker.AsyncIdleStateChanged += - (EventHandler)delegate(object sender, IdleStateEventArgs arguments) { - tracker.Track(Transaction.EndedDummy); - }; - - test1.End(); - } - } - - /// - /// Tests whether the progress tracker enters and leaves the idle state correctly - /// when a transaction is removed via Untrack() - /// - [Test] - public void TestIdleWithUntrack() { - using(ProgressTracker tracker = new ProgressTracker()) { - TestTransaction test1 = new TestTransaction(); - - Assert.IsTrue(tracker.Idle); - - tracker.Track(test1); - - Assert.IsFalse(tracker.Idle); - - tracker.Untrack(test1); - - Assert.IsTrue(tracker.Idle); - } - } - - /// - /// Tests whether the progress tracker enters and leaves the idle state correctly - /// when a transaction is removed the transaction finishing - /// - [Test] - public void TestIdleWithAutoRemoval() { - using(ProgressTracker tracker = new ProgressTracker()) { - TestTransaction test1 = new TestTransaction(); - - Assert.IsTrue(tracker.Idle); - - tracker.Track(test1); - - Assert.IsFalse(tracker.Idle); - - test1.End(); - - Assert.IsTrue(tracker.Idle); - } - } - - /// - /// Tests whether the progress tracker enters and leaves the idle state correctly - /// when a transaction is removed via Untrack() - /// - [Test] - public void TestProgressWithUntrack() { - using(ProgressTracker tracker = new ProgressTracker()) { - TestTransaction test1 = new TestTransaction(); - TestTransaction test2 = new TestTransaction(); - tracker.Track(test1); - tracker.Track(test2); - - Assert.AreEqual(0.0f, tracker.Progress); - - test1.ChangeProgress(0.5f); - - Assert.AreEqual(0.25f, tracker.Progress); - - tracker.Untrack(test2); - - Assert.AreEqual(0.5f, tracker.Progress); - } - } - - /// - /// Verifies that the progress tracker throws an exception if it is instructed - /// to untrack a transaction it doesn't know about - /// - [Test] - public void TestThrowOnUntrackNonTrackedTransaction() { - using(ProgressTracker tracker = new ProgressTracker()) { - TestTransaction test1 = new TestTransaction(); - - Assert.Throws( - delegate() { tracker.Untrack(test1); } - ); - } - } - - /// - /// Verifies that the progress tracker throws an exception if it is instructed - /// to untrack a transaction it doesn't know about - /// - [Test] - public void TestProgressReportDuringUnsubscribe() { - using(ProgressTracker tracker = new ProgressTracker()) { - EvilTransaction evil = new EvilTransaction(); - tracker.Track(evil); - tracker.Untrack(evil); - } - } - - /// - /// Verifies that the progress tracker doesn't choke on a transaction being tracked - /// multiple times. - /// - [Test] - public void TestMultiTrackedTransaction() { - using(ProgressTracker tracker = new ProgressTracker()) { - TestTransaction test = new TestTransaction(); - tracker.Track(test); - tracker.Track(test); - tracker.Track(test); - tracker.Untrack(test); - tracker.Untrack(test); - tracker.Untrack(test); - } - } - - /// Mocks a subscriber for the events of a tracker - /// Tracker to mock an event subscriber for - /// The mocked event subscriber - private Mock mockSubscriber(ProgressTracker tracker) { - Mock mockedSubscriber = - this.mockery.CreateMock(); - - tracker.AsyncIdleStateChanged += - new EventHandler(mockedSubscriber.MockObject.IdleStateChanged); - - tracker.AsyncProgressChanged += - new EventHandler(mockedSubscriber.MockObject.ProgressChanged); - - return mockedSubscriber; - } - - /// Mock object factory - private MockFactory mockery; - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/ProgressTracker.cs b/Source/Tracking/ProgressTracker.cs deleted file mode 100644 index a0a49f3..0000000 --- a/Source/Tracking/ProgressTracker.cs +++ /dev/null @@ -1,369 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Nuclex.Support.Tracking { - - /// - /// Helps tracking the progress of one or more background transactions - /// - /// - /// - /// This is useful if you want to display a progress bar for multiple - /// transactions but can not guarantee that no additional transactions - /// will appear inmidst of execution. - /// - /// - /// This class does not implement the interface itself - /// in order to not violate the design principles of transactions which - /// guarantee that a will only finish once (whereas the - /// progress tracker might 'finish' any number of times). - /// - /// - public class ProgressTracker : IDisposable, IProgressReporter { - - #region class TransactionMatcher - - /// Matches a direct transaction to a fully wrapped one - private class TransactionMatcher { - - /// - /// Initializes a new transaction matcher that matches against - /// the specified transaction - /// - /// Transaction to match against - public TransactionMatcher(Transaction toMatch) { - this.toMatch = toMatch; - } - - /// - /// Checks whether the provided transaction matches the comparison - /// transaction of the instance - /// - /// Transaction to match to the comparison transaction - public bool Matches(ObservedWeightedTransaction other) { - return ReferenceEquals(other.WeightedTransaction.Transaction, this.toMatch); - } - - /// Transaction this instance compares against - private Transaction toMatch; - - } - - #endregion // class TransactionMatcher - - /// Triggered when the idle state of the tracker changes - /// - /// The tracker is idle when no transactions are being tracked in it. If you're - /// using this class to feed a progress bar, this would be the event to use for - /// showing or hiding the progress bar. The tracker starts off as idle because, - /// upon construction, its list of transactions will be empty. - /// - public event EventHandler AsyncIdleStateChanged; - - /// Triggered when the total progress has changed - public event EventHandler AsyncProgressChanged; - - /// Initializes a new transaction tracker - public ProgressTracker() { - - this.trackedTransactions = new List>(); - this.idle = true; - - this.asyncEndedDelegate = - new ObservedWeightedTransaction.ReportDelegate(asyncEnded); - this.asyncProgressUpdatedDelegate = - new ObservedWeightedTransaction.ReportDelegate(asyncProgressChanged); - - } - - /// Immediately releases all resources owned by the instance - public void Dispose() { - lock(this.trackedTransactions) { - - // Get rid of all transactions we're tracking. This unsubscribes the - // observers from the events of the transactions and stops us from - // being kept alive and receiving any further events if some of the - // tracked transactions are still executing. - for(int index = 0; index < this.trackedTransactions.Count; ++index) - this.trackedTransactions[index].Dispose(); - - // Help the GC a bit by untangling the references :) - this.trackedTransactions.Clear(); - this.trackedTransactions = null; - - } // lock - } - - /// Begins tracking the specified background transactions - /// Background transaction to be tracked - public void Track(Transaction transaction) { - Track(transaction, 1.0f); - } - - /// Begins tracking the specified background transaction - /// Background transaction to be tracked - /// Weight to assign to this background transaction - public void Track(Transaction transaction, float weight) { - - // Add the new transaction into the tracking list. This has to be done - // inside a lock to prevent issues with the progressUpdate callback, which could - // access the totalWeight field before it has been updated to reflect the - // new transaction added to the collection. - lock(this.trackedTransactions) { - - bool wasEmpty = (this.trackedTransactions.Count == 0); - - if(transaction.Ended) { - - // If the ended transaction would become the only transaction in the list, - // there's no sense in doing anything at all because it would have to be - // thrown right out again. Only add the transaction when there are other - // running transactions to properly sum total progress for consistency. - if(!wasEmpty) { - - // Construct a new observation wrapper. This is done inside the lock - // because as soon as we are subscribed to the events, we can potentially - // receive them. The lock eliminates the risk of processing a progress update - // before the transaction has been added to the tracked transactions list. - this.trackedTransactions.Add( - new ObservedWeightedTransaction( - new WeightedTransaction(transaction, weight), - this.asyncProgressUpdatedDelegate, - this.asyncEndedDelegate - ) - ); - - } - - } else { // Not ended -- Transaction is still running - - // Construct a new transation observer and add the transaction to our - // list of tracked transactions. - ObservedWeightedTransaction observedTransaction = - new ObservedWeightedTransaction( - new WeightedTransaction(transaction, weight), - this.asyncProgressUpdatedDelegate, - this.asyncEndedDelegate - ); - - this.trackedTransactions.Add(observedTransaction); - - // If this is the first transaction to be added to the list, tell our - // owner that we're idle no longer! - if(wasEmpty) { - setIdle(false); - } - - } // if transaction ended - - // This can be done after we registered the wrapper to our delegates because - // any incoming progress updates will be stopped from the danger of a - // division-by-zero from the potentially still zeroed totalWeight by the lock. - this.totalWeight += weight; - - // All done, the total progress is different now, so force a recalculation and - // send out the AsyncProgressUpdated event. - recalculateProgress(); - - } // lock - - } - - /// Stops tracking the specified background transaction - /// Background transaction to stop tracking of - public void Untrack(Transaction transaction) { - lock(this.trackedTransactions) { - - // Locate the object to be untracked in our collection - int index; - for(index = 0; index < this.trackedTransactions.Count; ++index) { - bool same = ReferenceEquals( - transaction, - this.trackedTransactions[index].WeightedTransaction.Transaction - ); - if(same) { - break; - } - } - if(index == this.trackedTransactions.Count) { - throw new ArgumentException("Specified transaction is not being tracked"); - } - - // Remove and dispose the transaction the user wants to untrack - { - ObservedWeightedTransaction wrappedTransaction = - this.trackedTransactions[index]; - - this.trackedTransactions.RemoveAt(index); - wrappedTransaction.Dispose(); - } - - // If the list is empty, then we're back in the idle state - if(this.trackedTransactions.Count == 0) { - - this.totalWeight = 0.0f; - - // If we entered the idle state with this call, report the state change! - setIdle(true); - - } else { - - // Rebuild the total weight from scratch. Subtracting the removed transaction's - // weight would work, too, but we might accumulate rounding errors making the sum - // drift slowly away from the actual value. - float newTotalWeight = 0.0f; - for(index = 0; index < this.trackedTransactions.Count; ++index) - newTotalWeight += this.trackedTransactions[index].WeightedTransaction.Weight; - - this.totalWeight = newTotalWeight; - - recalculateProgress(); - - } - - } // lock - } - - /// Whether the tracker is currently idle - public bool Idle { - get { return this.idle; } - } - - /// Current summed progress of the tracked transactions - public float Progress { - get { return this.progress; } - } - - /// Fires the AsyncIdleStateChanged event - /// New idle state to report - protected virtual void OnAsyncIdleStateChanged(bool idle) { - EventHandler copy = AsyncIdleStateChanged; - if(copy != null) - copy(this, new IdleStateEventArgs(idle)); - } - - /// Fires the AsyncProgressUpdated event - /// New progress to report - protected virtual void OnAsyncProgressUpdated(float progress) { - EventHandler copy = AsyncProgressChanged; - if(copy != null) - copy(this, new ProgressReportEventArgs(progress)); - } - - /// Recalculates the total progress of the tracker - private void recalculateProgress() { - bool progressChanged = false; - - // Lock the collection to avoid trouble when someone tries to remove one - // of our tracked transactions while we're just doing a progress update - lock(this.trackedTransactions) { - - // This is a safety measure. In theory, even after all transactions have - // ended and the collection of tracked transactions is cleared, a waiting - // thread might deliver another progress update causing this method to - // be entered. In this case, the right thing is to do nothing at all. - if(this.totalWeight != 0.0f) { - float totalProgress = 0.0f; - - // Sum up the total progress - for(int index = 0; index < this.trackedTransactions.Count; ++index) { - float weight = this.trackedTransactions[index].WeightedTransaction.Weight; - totalProgress += this.trackedTransactions[index].Progress * weight; - } - - // This also needs to be in the lock to guarantee that the total weight - // corresponds to the number of transactions we just summed -- by design, - // the total weight always has to be updated at the same time as the collection. - totalProgress /= this.totalWeight; - - if(totalProgress != this.progress) { - this.progress = totalProgress; - progressChanged = true; - } - } - - } // lock - - // Finally, trigger the event if the progress has changed - if(progressChanged) { - OnAsyncProgressUpdated(this.progress); - } - } - - /// Called when one of the tracked transactions has ended - private void asyncEnded() { - lock(this.trackedTransactions) { - - // If any transactions in the list are still going, keep the entire list. - // This behavior is intentional in order to prevent the tracker's progress from - // jumping back repeatedly when multiple tracked transactions come to an end. - for(int index = 0; index < this.trackedTransactions.Count; ++index) - if(!this.trackedTransactions[index].WeightedTransaction.Transaction.Ended) - return; - - // All transactions have finished, get rid of the wrappers and make a - // fresh start for future transactions to be tracked. No need to call - // Dispose() since, as a matter of fact, when the transaction - this.trackedTransactions.Clear(); - this.totalWeight = 0.0f; - - // Notify our owner that we're idle now. This line is only reached when all - // transactions were finished, so it's safe to trigger this here. - setIdle(true); - - } // lock - } - - /// Called when one of the tracked transactions has achieved progress - private void asyncProgressChanged() { - recalculateProgress(); - } - - /// Changes the idle state - /// Whether or not the tracker is currently idle - /// - /// This method expects to be called during a lock() on trackedTransactions! - /// - private void setIdle(bool idle) { - this.idle = idle; - - OnAsyncIdleStateChanged(idle); - } - - /// Whether the tracker is currently idle - private volatile bool idle; - /// Current summed progress of the tracked transactions - private volatile float progress; - /// Total weight of all transactions being tracked - private volatile float totalWeight; - /// Transactions being tracked by this tracker - private List> trackedTransactions; - /// Delegate for the asyncEnded() method - private ObservedWeightedTransaction.ReportDelegate asyncEndedDelegate; - /// Delegate for the asyncProgressUpdated() method - private ObservedWeightedTransaction.ReportDelegate asyncProgressUpdatedDelegate; - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/Request.Test.cs b/Source/Tracking/Request.Test.cs deleted file mode 100644 index f97f642..0000000 --- a/Source/Tracking/Request.Test.cs +++ /dev/null @@ -1,134 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.IO; - -using Nuclex.Support.Scheduling; - -using NUnit.Framework; - -namespace Nuclex.Support.Tracking { - - /// Unit Test for the request class - [TestFixture] - public class RequestTest { - - #region class CustomWaitRequest - - /// - /// Request with a custom wait implementation that completes the request instead - /// of waiting for it complete by outside means - /// - private class CustomWaitRequest : Request { - - /// Waits until the background process finishes - public override void Wait() { - // This could be a race condition if this was used for anything but this simple - // unit test. Might be neccessary to refactor this when writing advanced tests. - if(!base.Ended) { - OnAsyncEnded(); - } - } - - } - - #endregion // class CustomWaitRequest - - /// - /// Verifies that the SucceededDummy request is in the ended state - /// - [Test] - public void TestSucceededDummy() { - Request dummy = Request.SucceededDummy; - - Assert.IsTrue(dummy.Ended); - dummy.Join(); // should not throw - } - - /// - /// Verifies that the FailedDummy request is in the ended state and throws - /// an exception when Join()ing - /// - [Test] - public void TestFailedDummy() { - Request failedDummy = Request.CreateFailedDummy( - new AbortedException("Hello World") - ); - - Assert.IsTrue(failedDummy.Ended); - Assert.Throws( - delegate() { failedDummy.Join(); } - ); - } - - /// - /// Verifies that the Request's Wait() method is invoked if the request is joined - /// before the request has finished. - /// - [Test] - public void TestJoinWithWaiting() { - CustomWaitRequest waitRequest = new CustomWaitRequest(); - waitRequest.Join(); - Assert.IsTrue(waitRequest.Ended); - } - - - } - - /// Unit Test for the generic request class - [TestFixture] - public class GenericRequestTest { - - /// - /// Verifies that the SucceededDummy request is in the ended state - /// - [Test] - public void TestSucceededDummy() { - Request dummy = Request.CreateSucceededDummy(12345); - - Assert.IsTrue(dummy.Ended); - Assert.AreEqual(12345, dummy.Join()); // should not throw - } - - /// - /// Verifies that the FailedDummy request is in the ended state and throws - /// an exception when Join()ing - /// - [Test] - public void TestFailedDummy() { - Request failedDummy = Request.CreateFailedDummy( - new AbortedException("Hello World") - ); - - Assert.IsTrue(failedDummy.Ended); - Assert.Throws( - delegate() { failedDummy.Join(); } - ); - } - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/Request.cs b/Source/Tracking/Request.cs deleted file mode 100644 index 47d481a..0000000 --- a/Source/Tracking/Request.cs +++ /dev/null @@ -1,217 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Tracking { - - /// Asynchronous request running in the background - /// - /// - /// If the background process fails, the exception that caused it to fail is - /// communicated to all parties waiting on the Request through the Join() - /// method. Implementers should store any errors occuring in the asynchronous - /// parts of their code in a try..catch block (or avoid throwing and just - /// store a new exception) and re-throw them when in ReraiseExceptions() - /// - /// - /// Like in the transaction class, the contract requires you to always call - /// OnAsyncEnded(), no matter what the outcome of your operation is. - /// - /// - public abstract class Request : Transaction { - - #region class EndedDummyRequest - - /// Dummy request that is always in the ended state - private class EndedDummyRequest : Request { - /// Creates a new successfully completed dummy request - public EndedDummyRequest() : this(null) { } - /// Creates a new failed dummy request - /// Exception that caused the dummy to fail - public EndedDummyRequest(Exception exception) { - this.exception = exception; - OnAsyncEnded(); - } - /// - /// Allows the specific request implementation to re-throw an exception if - /// the background process finished unsuccessfully - /// - protected override void ReraiseExceptions() { - if(this.exception != null) - throw this.exception; - } - /// Exception that supposedly caused the request to fail - private Exception exception; - } - - #endregion // EndedDummyRequest - - /// Succeeded dummy request - /// - /// Use to indicate success if the request has already been completed at - /// the time you are asked to perform it. - /// - public static readonly Request SucceededDummy = new EndedDummyRequest(); - - /// Creates a new failed dummy request - /// Exception that supposedly caused the request to fail - /// - /// A failed request that reports the provided exception as cause for its failure - /// - public static Request CreateFailedDummy(Exception exception) { - return new EndedDummyRequest(exception); - } - - /// Waits for the background operation to end - /// - /// Any exceptions raised in the background operation will be thrown - /// in this method. If you decide to override this method, you should - /// call Wait() first (and let any possible exception through to your - /// caller). - /// - public virtual void Join() { - - // If the request itself hasn't ended yet, block the caller until it has. - // We could just use WaitHandle.WaitOne() here, but since the WaitHandle is created - // on-the-fly only when it is requested, we can avoid the WaitHandle creation in - // case the request is already finished! - if(!Ended) - Wait(); - - // Allow the implementer to throw an exception in case an error has occured - ReraiseExceptions(); - - } - - /// - /// Allows the specific request implementation to re-throw an exception if - /// the background process finished unsuccessfully - /// - protected virtual void ReraiseExceptions() { } - - } - - /// Request providing a result that can be passed to the caller - /// - /// Type of the result being provided by the request - /// - public abstract class Request : Request { - - #region class SucceededDummyRequest - - /// Succeeded dummy request that is always in the ended state - private class SucceededDummyRequest : Request { - /// Creates a new failed dummy request - /// Result to return to the request's caller - public SucceededDummyRequest(ResultType result) { - this.result = result; - OnAsyncEnded(); - } - /// - /// Allows the specific request implementation to re-throw an exception if - /// the background process finished unsuccessfully - /// - protected override ResultType GatherResults() { - return this.result; - } - /// Results the succeede dummy request will provide to the caller - private ResultType result; - } - - #endregion // SucceededDummyRequest - - #region class FailedDummyRequest - - /// Failed dummy request that is always in the ended state - private class FailedDummyRequest : Request { - /// Creates a new failed dummy request - /// Exception that caused the dummy to fail - public FailedDummyRequest(Exception exception) { - this.exception = exception; - OnAsyncEnded(); - } - /// - /// Allows the specific request implementation to re-throw an exception if - /// the background process finished unsuccessfully - /// - protected override ResultType GatherResults() { - throw this.exception; - } - /// Exception that supposedly caused the request to fail - private Exception exception; - } - - #endregion // FailedDummyRequest - - /// Creates a new failed dummy request - /// Result to provide to the caller - /// - /// A succeeded request that returns the provided result to the caller - /// - public static Request CreateSucceededDummy(ResultType result) { - return new SucceededDummyRequest(result); - } - - /// Creates a new failed dummy request - /// Exception that supposedly caused the request to fail - /// - /// A failed request that reports the provided exception as cause for its failure - /// - public static new Request CreateFailedDummy(Exception exception) { - return new FailedDummyRequest(exception); - } - - /// Waits for the background operation to end - /// - /// Any exceptions raised in the background operation will be thrown - /// in this method. If you decide to override this method, you should - /// call End() first (and let any possible exception through to your - /// caller). - /// - public new ResultType Join() { - base.Join(); - - // Return the results of the request - return GatherResults(); - } - - /// - /// Allows the specific request implementation to re-throw an exception if - /// the background process finished unsuccessfully - /// - protected override void ReraiseExceptions() { - // Request and discard the result, so the implementor can do all error handling - // in the GatherResults() method. This is a good default implementation as long - // as the returned object does not require IDispose. It if does, this method - // needs to be overridden. - GatherResults(); - } - - /// - /// Allows the specific request to return the results of the Request to the - /// caller of the Join() method - /// - protected abstract ResultType GatherResults(); - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/StatusReportEventArgs.Test.cs b/Source/Tracking/StatusReportEventArgs.Test.cs deleted file mode 100644 index a66fbed..0000000 --- a/Source/Tracking/StatusReportEventArgs.Test.cs +++ /dev/null @@ -1,59 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.IO; - -using NUnit.Framework; - -namespace Nuclex.Support.Tracking { - - /// Unit Test for the status report event argument container - [TestFixture] - public class StatusReportEventArgsTest { - - /// - /// Tests whether the status report event arguments correctly reports an empty status - /// - [Test] - public void TestEmptyStatus() { - StatusReportEventArgs emptyStatus = new StatusReportEventArgs(string.Empty); - - Assert.AreEqual(string.Empty, emptyStatus.Status); - } - - /// - /// Tests whether the status report event arguments correctly reports simple - /// status indications - /// - [Test] - public void TestSimpleStatus() { - StatusReportEventArgs emptyStatus = new StatusReportEventArgs("hello world"); - - Assert.AreEqual("hello world", emptyStatus.Status); - } - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/StatusReportEventArgs.cs b/Source/Tracking/StatusReportEventArgs.cs deleted file mode 100644 index 0de4ac3..0000000 --- a/Source/Tracking/StatusReportEventArgs.cs +++ /dev/null @@ -1,51 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Tracking { - - /// Event arguments for reporting a status to the subscriber - public class StatusReportEventArgs : EventArgs { - - /// Initializes a new status report event arguments container - /// Status to report to the event's subscribers - public StatusReportEventArgs(string status) { - this.status = status; - } - - /// The currently reported status - /// - /// The contents of this string are up to the publisher of the event to - /// define. Though it is recommended to report the status as a human-readable - /// string, these strings might not in all cases be properly localized or - /// suitable for display in a GUI. - /// - public string Status { - get { return this.status; } - } - - /// Reported status - private string status; - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/Transaction.Test.cs b/Source/Tracking/Transaction.Test.cs deleted file mode 100644 index abc2f63..0000000 --- a/Source/Tracking/Transaction.Test.cs +++ /dev/null @@ -1,251 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; - -using NUnit.Framework; -using NMock; - -namespace Nuclex.Support.Tracking { - - /// Unit Test for the transaction class - [TestFixture] - public class TransactionTest { - - #region interface ITransactionSubscriber - - /// Interface used to test the transaction - public interface ITransactionSubscriber { - - /// Called when the set transaction has ended - /// Transaction group that as ended - /// Not used - void Ended(object sender, EventArgs arguments); - - } - - #endregion // interface ITransactionGroupSubscriber - - #region class TestTransaction - - /// Transaction used for testing in this unit test - private class TestTransaction : Transaction { - - /// Transitions the transaction into the ended state - public void End() { - OnAsyncEnded(); - } - - } - - #endregion // class TestWiatable - - #region class UnsubscribingTransaction - - /// Transaction that unsubscribes during an event callback - private class UnsubscribingTransaction : Transaction { - - /// Initializes a new unsubscribing transaction - /// - /// Transaction whose AsyncEnded event will be monitored to trigger - /// the this transaction unsubscribing from the event. - /// - public UnsubscribingTransaction(Transaction transactionToMonitor) { - this.transactionToMonitor = transactionToMonitor; - this.monitoredTransactionEndedDelegate = new EventHandler( - monitoredTransactionEnded - ); - - this.transactionToMonitor.AsyncEnded += this.monitoredTransactionEndedDelegate; - } - - /// Called when the monitored transaction has ended - /// Monitored transaction that has ended - /// Not used - private void monitoredTransactionEnded(object sender, EventArgs arguments) { - this.transactionToMonitor.AsyncEnded -= this.monitoredTransactionEndedDelegate; - } - - /// Transitions the transaction into the ended state - public void End() { - OnAsyncEnded(); - } - - /// Transaction whose ending in being monitored - private Transaction transactionToMonitor; - /// Delegate to the monitoredTransactionEnded() method - private EventHandler monitoredTransactionEndedDelegate; - - } - - #endregion // class TestWiatable - - /// Initialization routine executed before each test is run - [SetUp] - public void Setup() { - this.mockery = new MockFactory(); - } - - /// - /// Verifies that the transaction throws an exception when it is ended multiple times - /// - [Test] - public void TestThrowOnRepeatedlyEndedTransaction() { - TestTransaction test = new TestTransaction(); - test.End(); - Assert.Throws( - delegate() { test.End(); } - ); - } - - /// - /// Tests whether the Ended event of the transaction is correctly delivered if - /// the transaction ends after the subscription already took place - /// - [Test] - public void TestEndedEventAfterSubscription() { - TestTransaction test = new TestTransaction(); - - Mock mockedSubscriber = mockSubscriber(test); - mockedSubscriber.Expects.One.Method(m => m.Ended(null, null)).WithAnyArguments(); - - test.End(); - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - - /// - /// Tests whether the Ended event of the transaction is correctly delivered if - /// the transaction is already done when the subscription takes place - /// - [Test] - public void TestEndedEventDuingSubscription() { - TestTransaction test = new TestTransaction(); - test.End(); - - Mock mockedSubscriber = - this.mockery.CreateMock(); - - mockedSubscriber.Expects.One.Method(m => m.Ended(null, null)).WithAnyArguments(); - - test.AsyncEnded += new EventHandler(mockedSubscriber.MockObject.Ended); - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - - /// - /// Verifies that the Wait() method of the transaction works as expected - /// - [Test] - public void TestWaitUnlimited() { - TestTransaction test = new TestTransaction(); - - // We can only do a positive test here without slowing down the unit test - ThreadPool.QueueUserWorkItem( - (WaitCallback)delegate(object state) { Thread.Sleep(1); test.End(); } - ); - - test.Wait(); - } - - /// - /// Verifies that the Wait() method of the transaction works as expected using - /// a millisecond count as its argument - /// - [Test] - public void TestWaitMilliseconds() { - TestTransaction test = new TestTransaction(); - - // Wait 0 milliseconds for the transaction to end. Of course, this will not happen, - // so a timeout occurs and false is returned - Assert.IsFalse(test.Wait(0)); - - test.End(); - - // Wait another 0 milliseconds for the transaction to end. Now it has already ended - // and no timeout will occur, even with a wait time of 0 milliseconds. - Assert.IsTrue(test.Wait(0)); - } - - /// - /// Verifies that the Wait() method of the transaction works as expected using - /// a TimeSpan as its argument - /// - [Test] - public void TestWaitTimeSpan() { - TestTransaction test = new TestTransaction(); - - // Wait 0 milliseconds for the transaction to end. Of course, this will not happen, - // so a timeout occurs and false is returned - Assert.IsFalse(test.Wait(TimeSpan.Zero)); - - test.End(); - - // Wait another 0 milliseconds for the transaction to end. Now it has already ended - // and no timeout will occur, even with a wait time of 0 milliseconds. - Assert.IsTrue(test.Wait(TimeSpan.Zero)); - } - - /// - /// Verifies that no error occurs when an even subscriber to the AsyncEnded event - /// unsubscribes in the event callback handler - /// - [Test] - public void TestUnsubscribeInEndedCallback() { - TestTransaction monitored = new TestTransaction(); - UnsubscribingTransaction test = new UnsubscribingTransaction(monitored); - - Mock mockedSubscriber = mockSubscriber(monitored); - - try { - mockedSubscriber.Expects.One.Method(m => m.Ended(null, null)).WithAnyArguments(); - monitored.End(); - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - finally { - test.End(); - } - } - - /// Mocks a subscriber for the events of a transaction - /// Transaction to mock an event subscriber for - /// The mocked event subscriber - private Mock mockSubscriber(Transaction transaction) { - Mock mockedSubscriber = - this.mockery.CreateMock(); - - transaction.AsyncEnded += new EventHandler(mockedSubscriber.MockObject.Ended); - - return mockedSubscriber; - } - - /// Mock object factory - private MockFactory mockery; - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/Transaction.cs b/Source/Tracking/Transaction.cs deleted file mode 100644 index 2802bc1..0000000 --- a/Source/Tracking/Transaction.cs +++ /dev/null @@ -1,269 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Nuclex.Support.Tracking { - - /// Base class for background processes the user can wait on - /// - /// - /// By encapsulating long-running operations which will ideally be running in - /// a background thread in a class that's derived from - /// you can wait for the completion of the operation and optionally even receive - /// feedback on the achieved progress. This is useful for displaying a progress - /// bar, loading screen or some other means of entertaining the user while he - /// waits for the task to complete. - /// - /// - /// You can register callbacks which will be fired once the - /// task has completed. This class deliberately does not provide an Execute() - /// method or anything similar to clearly seperate the initiation of an operation - /// from just monitoring it. By omitting an Execute() method, it also becomes - /// possible to construct a transaction just-in-time when it is explicitely being - /// asked for. - /// - /// - public abstract class Transaction { - - #region class EndedDummyTransaction - - /// Dummy transaction which always is in the 'ended' state - private class EndedDummyTransaction : Transaction { - - /// Initializes a new ended dummy transaction - public EndedDummyTransaction() { - OnAsyncEnded(); - } - - } - - #endregion // class EndedDummyTransaction - - /// A dummy transaction that's always in the 'ended' state - /// - /// Useful if an operation is already complete when it's being asked for or - /// when a transaction that's lazily created is accessed after the original - /// operation has ended already. - /// - public static readonly Transaction EndedDummy = new EndedDummyTransaction(); - - /// Will be triggered when the transaction has ended - /// - /// If the process is already finished when a client registers to this event, - /// the registered callback will be invoked synchronously right when the - /// registration takes place. - /// - public virtual event EventHandler AsyncEnded { - add { - - // If the background process has not yet ended, add the delegate to the - // list of subscribers. This uses the double-checked locking idiom to - // avoid taking the lock when the background process has already ended. - if(!this.ended) { - lock(this) { - if(!this.ended) { - - // The subscriber list is also created lazily ;-) - if(ReferenceEquals(this.endedEventSubscribers, null)) { - this.endedEventSubscribers = new List(); - } - - // Subscribe the event handler to the list - this.endedEventSubscribers.Add(value); - return; - - } - } - } - - // If this point is reached, the background process was already finished - // and we have to invoke the subscriber manually as promised. - value(this, EventArgs.Empty); - - } - remove { - - if(!this.ended) { - lock(this) { - if(!this.ended) { - - // Only try to remove the event handler if the subscriber list was created, - // otherwise, we can be sure that no actual subscribers exist. - if(!ReferenceEquals(this.endedEventSubscribers, null)) { - int eventHandlerIndex = this.endedEventSubscribers.IndexOf(value); - - // Unsubscribing a non-subscribed delegate from an event is allowed and - // should not throw an exception. - if(eventHandlerIndex != -1) { - this.endedEventSubscribers.RemoveAt(eventHandlerIndex); - } - } - - } - } - } - - } - } - - /// Waits until the background process finishes - public virtual void Wait() { - if(!this.ended) { - WaitHandle.WaitOne(); - } - } - -#if WINDOWS - - /// Waits until the background process finishes or a timeout occurs - /// - /// Time span after which to stop waiting and return immediately - /// - /// - /// True if the background process completed, false if the timeout was reached - /// - public virtual bool Wait(TimeSpan timeout) { - if(this.ended) { - return true; - } - - return WaitHandle.WaitOne(timeout, false); - } - -#endif // WINDOWS - - /// Waits until the background process finishes or a timeout occurs - /// - /// Number of milliseconds after which to stop waiting and return immediately - /// - /// - /// True if the background process completed, false if the timeout was reached - /// - public virtual bool Wait(int timeoutMilliseconds) { - if(this.ended) { - return true; - } - -#if WINDOWS - return WaitHandle.WaitOne(timeoutMilliseconds, false); -#else - return WaitHandle.WaitOne(timeoutMilliseconds); -#endif - } - - /// Whether the transaction has ended already - public virtual bool Ended { - get { return this.ended; } - } - - /// WaitHandle that can be used to wait for the transaction to end - public virtual WaitHandle WaitHandle { - get { - - // The WaitHandle will only be created when someone asks for it! - // We can *not* optimize this lock away since we absolutely must not create - // two doneEvents -- someone might call .WaitOne() on the first one when only - // the second one is referenced by this.doneEvent and thus gets set in the end. - if(this.doneEvent == null) { - lock(this) { - if(this.doneEvent == null) { - this.doneEvent = new ManualResetEvent(this.ended); - } - } - } - - // We can be sure the doneEvent has been created now! - return this.doneEvent; - - } - } - - /// Fires the AsyncEnded event - /// - /// - /// This event should be fired by the implementing class when its work is completed. - /// It's of no interest to this class whether the outcome of the process was - /// successfull or not, the outcome and results of the process taking place both - /// need to be communicated seperately. - /// - /// - /// Calling this method is mandatory. Implementers need to take care that - /// the OnAsyncEnded() method is called on any instance of transaction that's - /// being created. This method also must not be called more than once. - /// - /// - protected virtual void OnAsyncEnded() { - - // Make sure the transaction is not ended more than once. By guaranteeing that - // a transaction can only be ended once, we allow users of this class to - // skip some safeguards against notifications arriving twice. - lock(this) { - - // No double lock here, this is an exception that indicates an implementation - // error that will not be triggered under normal circumstances. We don't need - // to waste any effort optimizing the speed at which an implementation fault - // will be reported ;-) - if(this.ended) - throw new InvalidOperationException("The transaction has already been ended"); - - this.ended = true; - - // Doesn't really need a lock: if another thread wins the race and creates - // the event after we just saw it being null, it would be created in an already - // set state due to the ended flag (see above) being set to true beforehand! - // But since we've got a lock ready, we can even avoid that 1 in a million - // performance loss and prevent the doneEvent from being signalled needlessly. - if(this.doneEvent != null) - this.doneEvent.Set(); - - } - - // Fire the ended events to all event subscribers. We can freely use the list - // without synchronization at this point on since once this.ended is set to true, - // the subscribers list will not be accessed any longer - if(!ReferenceEquals(this.endedEventSubscribers, null)) { - for(int index = 0; index < this.endedEventSubscribers.Count; ++index) { - this.endedEventSubscribers[index](this, EventArgs.Empty); - } - this.endedEventSubscribers = null; - } - - } - - /// Event handlers which have subscribed to the ended event - /// - /// Does not need to be volatile since it's only accessed inside - /// - protected volatile List endedEventSubscribers; - /// Whether the operation has completed yet - protected volatile bool ended; - /// Event that will be set when the transaction is completed - /// - /// This event is will only be created when it is specifically asked for using - /// the WaitHandle property. - /// - protected volatile ManualResetEvent doneEvent; - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/TransactionGroup.Test.cs b/Source/Tracking/TransactionGroup.Test.cs deleted file mode 100644 index d77a8e9..0000000 --- a/Source/Tracking/TransactionGroup.Test.cs +++ /dev/null @@ -1,386 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; - -#if UNITTEST - -using NUnit.Framework; -using NMock; - -namespace Nuclex.Support.Tracking { - - /// Unit Test for the transaction group class - [TestFixture] - public class TransactionGroupTest { - - #region interface ITransactionGroupSubscriber - - /// Interface used to test the transaction group - public interface ITransactionGroupSubscriber { - - /// Called when the transaction group's progress changes - /// Transaction group whose progress has changed - /// Contains the new progress achieved - void ProgressChanged(object sender, ProgressReportEventArgs arguments); - - /// Called when the transaction group has ended - /// Transaction group that as ended - /// Not used - void Ended(object sender, EventArgs arguments); - - } - - #endregion // interface ITransactionGroupSubscriber - - #region class ProgressUpdateEventArgsMatcher - - /// Compares two ProgressUpdateEventArgsInstances for NMock validation - private class ProgressUpdateEventArgsMatcher : Matcher { - - /// Initializes a new ProgressUpdateEventArgsMatcher - /// Expected progress update event arguments - public ProgressUpdateEventArgsMatcher(ProgressReportEventArgs expected) { - this.expected = expected; - } - - /// - /// Called by NMock to verfiy the ProgressUpdateEventArgs match the expected value - /// - /// Actual value to compare to the expected value - /// - /// True if the actual value matches the expected value; otherwise false - /// - public override bool Matches(object actualAsObject) { - ProgressReportEventArgs actual = (actualAsObject as ProgressReportEventArgs); - if(actual == null) - return false; - - return (actual.Progress == this.expected.Progress); - } - - /// Creates a string representation of the expected value - /// Writer to write the string representation into - public override void DescribeTo(TextWriter writer) { - writer.Write(this.expected.Progress.ToString()); - } - - /// Expected progress update event args value - private ProgressReportEventArgs expected; - - } - - #endregion // class ProgressUpdateEventArgsMatcher - - #region class TestTransaction - - /// Transaction used for testing in this unit test - private class TestTransaction : Transaction, IProgressReporter { - - /// will be triggered to report when progress has been achieved - public event EventHandler AsyncProgressChanged; - - /// Changes the testing transaction's indicated progress - /// - /// New progress to be reported by the testing transaction - /// - public void ChangeProgress(float progress) { - OnAsyncProgressChanged(progress); - } - - /// Transitions the transaction into the ended state - public void End() { - OnAsyncEnded(); - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this transaction about the achieved progress. - /// - protected virtual void OnAsyncProgressChanged(float progress) { - OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this transaction about the achieved progress. - /// Allows for classes derived from the transaction class to easily provide - /// a custom event arguments class that has been derived from the - /// transaction's ProgressUpdateEventArgs class. - /// - protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { - EventHandler copy = AsyncProgressChanged; - if(copy != null) - copy(this, eventArguments); - } - - } - - #endregion // class TestTransaction - - #region class ChainEndingTransaction - - /// - /// Transaction that ends another transaction when its Ended property is called - /// - private class ChainEndingTransaction : Transaction { - - /// Initializes a new chain ending transaction - public ChainEndingTransaction() { - this.chainedTransaction = new TestTransaction(); - } - - /// Transitions the transaction into the ended state - public void End() { - OnAsyncEnded(); - } - - /// - /// Transaction that will end when this transaction's ended property is accessed - /// - public TestTransaction ChainedTransaction { - get { return this.chainedTransaction; } - } - - /// Whether the transaction has ended already - public override bool Ended { - get { - if(Interlocked.Exchange(ref this.endedCalled, 1) == 0) { - this.chainedTransaction.End(); - } - - return base.Ended; - } - } - - /// - /// Transaction that will end when this transaction's ended property is accessed - /// - private TestTransaction chainedTransaction; - - /// Whether we already ended the chained transaction and ourselves - private int endedCalled; - - } - - #endregion // class ChainEndingTransaction - - /// Initialization routine executed before each test is run - [SetUp] - public void Setup() { - this.mockery = new MockFactory(); - } - - /// Validates that the transaction group correctly sums the progress - [Test] - public void TestSummedProgress() { - using( - TransactionGroup testTransactionGroup = - new TransactionGroup( - new TestTransaction[] { new TestTransaction(), new TestTransaction() } - ) - ) { - Mock mockedSubscriber = mockSubscriber(testTransactionGroup); - - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(TransactionGroup)), - new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f)) - ); - - testTransactionGroup.Children[0].Transaction.ChangeProgress(0.5f); - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - } - - /// Validates that the transaction group respects the weights - [Test] - public void TestWeightedSummedProgress() { - using( - TransactionGroup testTransactionGroup = - new TransactionGroup( - new WeightedTransaction[] { - new WeightedTransaction(new TestTransaction(), 1.0f), - new WeightedTransaction(new TestTransaction(), 2.0f) - } - ) - ) { - Mock mockedSubscriber = mockSubscriber(testTransactionGroup); - - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(TransactionGroup)), - new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f / 3.0f)) - ); - - testTransactionGroup.Children[0].Transaction.ChangeProgress(0.5f); - - mockedSubscriber.Expects.One.Method(m => m.ProgressChanged(null, null)).With( - new NMock.Matchers.TypeMatcher(typeof(TransactionGroup)), - new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f)) - ); - - testTransactionGroup.Children[1].Transaction.ChangeProgress(0.5f); - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - } - - /// - /// Validates that the ended event is triggered when the last transaction out of - /// multiple transactions in the group ends. - /// - [Test] - public void TestEndedEventWithTwoTransactions() { - using( - TransactionGroup testTransactionGroup = - new TransactionGroup( - new TestTransaction[] { new TestTransaction(), new TestTransaction() } - ) - ) { - Mock mockedSubscriber = mockSubscriber(testTransactionGroup); - - mockedSubscriber.Expects.Exactly(2).Method( - m => m.ProgressChanged(null, null) - ).WithAnyArguments(); - - mockedSubscriber.Expects.One.Method( - m => m.Ended(null, null) - ).WithAnyArguments(); - - testTransactionGroup.Children[0].Transaction.End(); - testTransactionGroup.Children[1].Transaction.End(); - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - } - - /// - /// Validates that the ended event is triggered when a single transaction contained - /// in the group ends. - /// - [Test] - public void TestEndedEventWithSingleTransaction() { - using( - TransactionGroup testTransactionGroup = - new TransactionGroup( - new TestTransaction[] { new TestTransaction() } - ) - ) { - Mock mockedSubscriber = mockSubscriber(testTransactionGroup); - - mockedSubscriber.Expects.One.Method( - m => m.ProgressChanged(null, null) - ).WithAnyArguments(); - - mockedSubscriber.Expects.One.Method( - m => m.Ended(null, null) - ).WithAnyArguments(); - - - testTransactionGroup.Children[0].Transaction.End(); - - this.mockery.VerifyAllExpectationsHaveBeenMet(); - } - } - - /// - /// Verifies that the transaction group immediately enters the ended state when - /// the contained transactions have already ended before the constructor - /// - /// - /// This was a bug at one time and should prevent a regression - /// - [Test] - public void TestAlreadyEndedTransactions() { - using( - TransactionGroup testTransactionGroup = - new TransactionGroup( - new Transaction[] { Transaction.EndedDummy, Transaction.EndedDummy } - ) - ) { - Assert.IsTrue(testTransactionGroup.Wait(1000)); - } - } - - /// - /// Verifies that the transaction group doesn't think it's already ended when - /// the first transaction being added is in the ended state - /// - /// - /// This was a bug at one time and should prevent a regression - /// - [Test] - public void TestAlreadyEndedTransactionAsFirstTransaction() { - using( - TransactionGroup testTransactionGroup = - new TransactionGroup( - new Transaction[] { Transaction.EndedDummy, new TestTransaction() } - ) - ) { - Assert.IsFalse(testTransactionGroup.Ended); - } - } - - /// - /// Verifies that a transaction ending while the constructor is running doesn't - /// wreak havoc on the transaction group - /// - [Test] - public void TestTransactionEndingDuringConstructor() { - ChainEndingTransaction chainTransaction = new ChainEndingTransaction(); - using( - TransactionGroup testTransactionGroup = - new TransactionGroup( - new Transaction[] { chainTransaction.ChainedTransaction, chainTransaction } - ) - ) { - Assert.IsFalse(testTransactionGroup.Ended); - chainTransaction.End(); - Assert.IsTrue(testTransactionGroup.Ended); - } - } - - /// Mocks a subscriber for the events of a transaction - /// Transaction to mock an event subscriber for - /// The mocked event subscriber - private Mock mockSubscriber(Transaction transaction) { - Mock mockedSubscriber = - this.mockery.CreateMock(); - - transaction.AsyncEnded += new EventHandler(mockedSubscriber.MockObject.Ended); - (transaction as IProgressReporter).AsyncProgressChanged += - new EventHandler(mockedSubscriber.MockObject.ProgressChanged); - - return mockedSubscriber; - } - - /// Mock object factory - private MockFactory mockery; - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/TransactionGroup.cs b/Source/Tracking/TransactionGroup.cs deleted file mode 100644 index e5eb7fd..0000000 --- a/Source/Tracking/TransactionGroup.cs +++ /dev/null @@ -1,235 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Threading; - -using Nuclex.Support.Collections; - -namespace Nuclex.Support.Tracking { - - /// Forms a single transaction from a group of transactions - /// Type of transactions to manage as a set - public class TransactionGroup : Transaction, IDisposable, IProgressReporter - where TransactionType : Transaction { - - /// will be triggered to report when progress has been achieved - public event EventHandler AsyncProgressChanged; - - /// Initializes a new transaction group - /// Transactions to track with this group - /// - /// Uses a default weighting factor of 1.0 for all transactions. - /// - public TransactionGroup(IEnumerable children) { - List> childrenList = - new List>(); - - // Construct a WeightedTransaction with the default weight for each - // transaction and wrap it in an ObservedTransaction - foreach(TransactionType transaction in children) { - childrenList.Add( - new ObservedWeightedTransaction( - new WeightedTransaction(transaction), - new ObservedWeightedTransaction.ReportDelegate( - asyncProgressUpdated - ), - new ObservedWeightedTransaction.ReportDelegate( - asyncChildEnded - ) - ) - ); - } - - // Since all transactions have a weight of 1.0, the total weight is - // equal to the number of transactions in our list - this.totalWeight = (float)childrenList.Count; - // Thread.MemoryBarrier(); // not needed because children is volatile - this.children = childrenList; - - // Any asyncEnded events being receiving from the transactions until now - // would have been ignored, so we need to check again here - asyncChildEnded(); - } - - /// Initializes a new transaction group - /// Transactions to track with this group - public TransactionGroup( - IEnumerable> children - ) { - List> childrenList = - new List>(); - - // Construct an ObservedTransaction around each of the WeightedTransactions - foreach(WeightedTransaction transaction in children) { - childrenList.Add( - new ObservedWeightedTransaction( - transaction, - new ObservedWeightedTransaction.ReportDelegate( - asyncProgressUpdated - ), - new ObservedWeightedTransaction.ReportDelegate( - asyncChildEnded - ) - ) - ); - - // Sum up the total weight - this.totalWeight += transaction.Weight; - } - - this.children = childrenList; - - // Any asyncEnded events being receiving from the transactions until now - // would have been ignored, so we need to check again here - asyncChildEnded(); - } - - /// Immediately releases all resources owned by the object - public void Dispose() { - - if(this.children != null) { - - // Dispose all the observed transactions, disconnecting the events from the - // actual transactions so the GC can more easily collect this class - for(int index = 0; index < this.children.Count; ++index) - this.children[index].Dispose(); - - this.children = null; - this.wrapper = null; - - } - - } - - /// Childs contained in the transaction set - public IList> Children { - get { - - // The wrapper is constructed only when needed. Most of the time, users will - // just create a transaction group and monitor its progress without ever using - // the Childs collection. - if(this.wrapper == null) { - - // This doesn't need a lock because it's a stateless wrapper. - // If it is constructed twice, then so be it, no problem at all. - this.wrapper = new WeightedTransactionWrapperCollection( - this.children - ); - - } - - return this.wrapper; - - } - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this transaction about the achieved progress. - /// - protected virtual void OnAsyncProgressChanged(float progress) { - OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); - } - - /// Fires the progress update event - /// Progress to report (ranging from 0.0 to 1.0) - /// - /// Informs the observers of this transaction about the achieved progress. - /// Allows for classes derived from the transaction class to easily provide - /// a custom event arguments class that has been derived from the - /// transaction's ProgressUpdateEventArgs class. - /// - protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { - EventHandler copy = AsyncProgressChanged; - if(copy != null) - copy(this, eventArguments); - } - - /// - /// Called when the progress of one of the observed transactions changes - /// - private void asyncProgressUpdated() { - if(this.children == null) { - return; - } - - float totalProgress = 0.0f; - - // Calculate the sum of the progress reported by our child transactions, - // scaled to the weight each transaction has assigned to it. - for(int index = 0; index < this.children.Count; ++index) { - totalProgress += - this.children[index].Progress * this.children[index].WeightedTransaction.Weight; - } - - // Calculate the actual combined progress - if(this.totalWeight > 0.0f) - totalProgress /= this.totalWeight; - - // Send out the progress update - OnAsyncProgressChanged(totalProgress); - } - - /// - /// Called when an observed transaction ends - /// - private void asyncChildEnded() { - - // If a transaction reports its end durign the constructor, it will end up here - // where the collection has not been assigned yet, allowing us to skip the - // check until all transactions are there (otherwise, we might invoke - // OnAsyncended() early, because all transactions in the list seem to have ended - // despite the fact that the constructor hasn't finished adding transactions yet) - if(this.children == null) { - return; - } - - // If there's still at least one transaction going, don't report that - // the transaction group has finished yet. - for(int index = 0; index < this.children.Count; ++index) - if(!this.children[index].WeightedTransaction.Transaction.Ended) - return; - - // All child transactions have ended, so the set has now ended as well - if(Interlocked.Exchange(ref this.endedCalled, 1) == 0) { - OnAsyncEnded(); - } - - } - - /// Transactions being managed in the set - private volatile List> children; - /// - /// Wrapper collection for exposing the child transactions under the - /// WeightedTransaction interface - /// - private volatile WeightedTransactionWrapperCollection wrapper; - /// Summed weight of all transactions in the set - private float totalWeight; - /// Whether we already called OnAsyncEnded - private int endedCalled; - - } - -} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/WeightedTransaction.Test.cs b/Source/Tracking/WeightedTransaction.Test.cs deleted file mode 100644 index 316b5b7..0000000 --- a/Source/Tracking/WeightedTransaction.Test.cs +++ /dev/null @@ -1,87 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -#if UNITTEST - -using System; -using System.IO; - -using NUnit.Framework; - -namespace Nuclex.Support.Tracking { - - /// Unit Test for the weighted transaction wrapper - [TestFixture] - public class WeightedTransactionTest { - - #region class TestTransaction - - /// Transaction used for testing in this unit test - private class TestTransaction : Transaction { } - - #endregion // class TestTransaction - - /// - /// Tests whether the weighted transaction wrapper correctly stores the transaction - /// it was given in the constructor - /// - [Test] - public void TestTransactionStorage() { - TestTransaction transaction = new TestTransaction(); - WeightedTransaction testWrapper = new WeightedTransaction( - transaction - ); - - Assert.AreSame(transaction, testWrapper.Transaction); - } - - /// - /// Tests whether the weighted transaction wrapper correctly applies the default - /// unit weight to the transaction if no explicit weight was specified - /// - [Test] - public void TestDefaultWeight() { - TestTransaction transaction = new TestTransaction(); - WeightedTransaction testWrapper = new WeightedTransaction( - transaction - ); - - Assert.AreEqual(1.0f, testWrapper.Weight); - } - - /// - /// Tests whether the weighted transaction wrapper correctly stores the weight - /// it was given in the constructor - /// - [Test] - public void TestWeightStorage() { - TestTransaction transaction = new TestTransaction(); - WeightedTransaction testWrapper = new WeightedTransaction( - transaction, 12.0f - ); - - Assert.AreEqual(12.0f, testWrapper.Weight); - } - - } - -} // namespace Nuclex.Support.Tracking - -#endif // UNITTEST diff --git a/Source/Tracking/WeightedTransaction.cs b/Source/Tracking/WeightedTransaction.cs deleted file mode 100644 index eb0b8c6..0000000 --- a/Source/Tracking/WeightedTransaction.cs +++ /dev/null @@ -1,60 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2010 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; - -namespace Nuclex.Support.Tracking { - - /// Transaction with an associated weight for the total progress - public class WeightedTransaction where TransactionType : Transaction { - - /// - /// Initializes a new weighted transaction with a default weight of 1.0 - /// - /// Transaction whose progress to monitor - public WeightedTransaction(TransactionType transaction) : this(transaction, 1.0f) { } - - /// Initializes a new weighted transaction - /// transaction whose progress to monitor - /// Weighting of the transaction's progress - public WeightedTransaction(TransactionType transaction, float weight) { - this.transaction = transaction; - this.weight = weight; - } - - /// Transaction being wrapped by this weighted transaction - public TransactionType Transaction { - get { return this.transaction; } - } - - /// The contribution of this transaction to the total progress - public float Weight { - get { return this.weight; } - } - - /// Transaction whose progress we're tracking - private TransactionType transaction; - /// Weighting of this transaction in the total progress - private float weight; - - } - -} // namespace Nuclex.Support.Tracking