All remaining references to the old name of the waitable class, 'progression' changed to 'waitable'; QueueOperation renamed to OperationQueue; SetProgression renamed to WaitableGroup; improved documentation in various places

git-svn-id: file:///srv/devel/repo-conversion/nusu@78 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2008-06-11 20:28:08 +00:00
parent df860b8e57
commit 7bff8c5d52
12 changed files with 307 additions and 271 deletions

View File

@ -151,9 +151,9 @@
<Compile Include="Source\Scheduling\AbortedException.cs" /> <Compile Include="Source\Scheduling\AbortedException.cs" />
<Compile Include="Source\Scheduling\IAbortable.cs" /> <Compile Include="Source\Scheduling\IAbortable.cs" />
<Compile Include="Source\Scheduling\Operation.cs" /> <Compile Include="Source\Scheduling\Operation.cs" />
<Compile Include="Source\Scheduling\QueueOperation.cs" /> <Compile Include="Source\Scheduling\OperationQueue.cs" />
<Compile Include="Source\Scheduling\QueueOperation.Test.cs"> <Compile Include="Source\Scheduling\OperationQueue.Test.cs">
<DependentUpon>QueueOperation.cs</DependentUpon> <DependentUpon>OperationQueue.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Scheduling\ThreadCallbackOperation.cs" /> <Compile Include="Source\Scheduling\ThreadCallbackOperation.cs" />
<Compile Include="Source\Scheduling\ThreadOperation.cs" /> <Compile Include="Source\Scheduling\ThreadOperation.cs" />
@ -178,9 +178,9 @@
<DependentUpon>ProgressTracker.cs</DependentUpon> <DependentUpon>ProgressTracker.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Tracking\Request.cs" /> <Compile Include="Source\Tracking\Request.cs" />
<Compile Include="Source\Tracking\SetProgression.cs" /> <Compile Include="Source\Tracking\WaitableGroup.cs" />
<Compile Include="Source\Tracking\SetProgression.Test.cs"> <Compile Include="Source\Tracking\WaitableGroup.Test.cs">
<DependentUpon>SetProgression.cs</DependentUpon> <DependentUpon>WaitableGroup.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Tracking\StatusReportEventArgs.cs" /> <Compile Include="Source\Tracking\StatusReportEventArgs.cs" />
<Compile Include="Source\Tracking\Waitable.cs" /> <Compile Include="Source\Tracking\Waitable.cs" />

View File

@ -57,7 +57,7 @@ namespace Nuclex.Support.Collections {
} }
/// <summary> /// <summary>
/// Inserts an element into the ProgressionCollection at the specified index /// Inserts an element into the ObservableCollection at the specified index
/// </summary> /// </summary>
/// <param name="index"> /// <param name="index">
/// The object to insert. The value can be null for reference types. /// The object to insert. The value can be null for reference types.
@ -70,7 +70,7 @@ namespace Nuclex.Support.Collections {
} }
/// <summary> /// <summary>
/// Removes the element at the specified index of the ProgressionCollection /// Removes the element at the specified index of the ObservableCollection
/// </summary> /// </summary>
/// <param name="index">The zero-based index of the element to remove</param> /// <param name="index">The zero-based index of the element to remove</param>
protected override void RemoveItem(int index) { protected override void RemoveItem(int index) {

View File

@ -33,11 +33,11 @@ namespace Nuclex.Support.Scheduling {
/// <summary>Unit Test for the queue operation class</summary> /// <summary>Unit Test for the queue operation class</summary>
[TestFixture] [TestFixture]
public class QueueOperationTest { public class OperationQueueTest {
#region interface IQueueOperationSubscriber #region interface IQueueOperationSubscriber
/// <summary>Interface used to test the set progression.</summary> /// <summary>Interface used to test the set waitable.</summary>
public interface IQueueOperationSubscriber { public interface IQueueOperationSubscriber {
/// <summary>Called when the queue operations's progress changes</summary> /// <summary>Called when the queue operations's progress changes</summary>
@ -95,7 +95,7 @@ namespace Nuclex.Support.Scheduling {
#region class TestOperation #region class TestOperation
/// <summary>Progression used for testing in this unit test</summary> /// <summary>Operation used for testing in this unit test</summary>
private class TestOperation : Operation, IProgressReporter { private class TestOperation : Operation, IProgressReporter {
/// <summary>will be triggered to report when progress has been achieved</summary> /// <summary>will be triggered to report when progress has been achieved</summary>
@ -116,9 +116,9 @@ namespace Nuclex.Support.Scheduling {
OnAsyncEnded(); OnAsyncEnded();
} }
/// <summary>Changes the testing progression's indicated progress</summary> /// <summary>Changes the testing operation's indicated progress</summary>
/// <param name="progress"> /// <param name="progress">
/// New progress to be reported by the testing progression /// New progress to be reported by the testing operation
/// </param> /// </param>
public void ChangeProgress(float progress) { public void ChangeProgress(float progress) {
OnAsyncProgressChanged(progress); OnAsyncProgressChanged(progress);
@ -136,7 +136,7 @@ namespace Nuclex.Support.Scheduling {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this operation about the achieved progress.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(float progress) { protected virtual void OnAsyncProgressChanged(float progress) {
OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
@ -145,10 +145,10 @@ namespace Nuclex.Support.Scheduling {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this operation about the achieved progress.
/// Allows for classes derived from the Progression class to easily provide /// Allows for classes derived from the Operation class to easily provide
/// a custom event arguments class that has been derived from the /// a custom event arguments class that has been derived from the
/// Progression's ProgressUpdateEventArgs class. /// operation's ProgressUpdateEventArgs class.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged; EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged;
@ -175,8 +175,8 @@ namespace Nuclex.Support.Scheduling {
TestOperation operation1 = new TestOperation(); TestOperation operation1 = new TestOperation();
TestOperation operation2 = new TestOperation(); TestOperation operation2 = new TestOperation();
QueueOperation<TestOperation> testQueueOperation = OperationQueue<TestOperation> testQueueOperation =
new QueueOperation<TestOperation>( new OperationQueue<TestOperation>(
new TestOperation[] { operation1, operation2 } new TestOperation[] { operation1, operation2 }
); );
@ -188,7 +188,7 @@ namespace Nuclex.Support.Scheduling {
Method("ProgressChanged"). Method("ProgressChanged").
With( With(
new Matcher[] { new Matcher[] {
new NMock2.Matchers.TypeMatcher(typeof(QueueOperation<TestOperation>)), new NMock2.Matchers.TypeMatcher(typeof(OperationQueue<TestOperation>)),
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f)) new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f))
} }
); );
@ -199,7 +199,7 @@ namespace Nuclex.Support.Scheduling {
Method("ProgressChanged"). Method("ProgressChanged").
With( With(
new Matcher[] { new Matcher[] {
new NMock2.Matchers.TypeMatcher(typeof(QueueOperation<TestOperation>)), new NMock2.Matchers.TypeMatcher(typeof(OperationQueue<TestOperation>)),
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f)) new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f))
} }
); );

View File

@ -29,7 +29,7 @@ namespace Nuclex.Support.Scheduling {
/// <typeparam name="OperationType"> /// <typeparam name="OperationType">
/// Type of the child operations the QueueOperation will contain /// Type of the child operations the QueueOperation will contain
/// </typeparam> /// </typeparam>
public class QueueOperation<OperationType> : Operation, IProgressReporter public class OperationQueue<OperationType> : Operation, IProgressReporter
where OperationType : Operation { where OperationType : Operation {
/// <summary>will be triggered to report when progress has been achieved</summary> /// <summary>will be triggered to report when progress has been achieved</summary>
@ -40,24 +40,24 @@ namespace Nuclex.Support.Scheduling {
/// <remarks> /// <remarks>
/// All child operations will have a default weight of 1.0 /// All child operations will have a default weight of 1.0
/// </remarks> /// </remarks>
public QueueOperation(IEnumerable<OperationType> childs) : this() { public OperationQueue(IEnumerable<OperationType> childs) : this() {
// Construct a WeightedProgression with the default weight for each // Construct a WeightedWaitable with the default weight for each
// progression and wrap it in an ObservedProgression // waitable and wrap it in an ObservedWaitable
foreach(OperationType operation in childs) foreach(OperationType operation in childs)
this.children.Add(new WeightedWaitable<OperationType>(operation)); this.children.Add(new WeightedWaitable<OperationType>(operation));
// Since all progressions have a weight of 1.0, the total weight is // Since all waitables have a weight of 1.0, the total weight is
// equal to the number of progressions in our list // equal to the number of waitables in our list
this.totalWeight = (float)this.children.Count; this.totalWeight = (float)this.children.Count;
} }
/// <summary>Initializes a new queue operation with custom weights</summary> /// <summary>Initializes a new queue operation with custom weights</summary>
/// <param name="childs">Child operations to execute in this operation</param> /// <param name="childs">Child operations to execute in this operation</param>
public QueueOperation(IEnumerable<WeightedWaitable<OperationType>> childs) : this() { public OperationQueue(IEnumerable<WeightedWaitable<OperationType>> childs) : this() {
// Construct an ObservedProgression around each of the WeightedProgressions // Construct an ObservedWaitablen around each of the WeightedWaitables
foreach(WeightedWaitable<OperationType> operation in childs) { foreach(WeightedWaitable<OperationType> operation in childs) {
this.children.Add(operation); this.children.Add(operation);
@ -68,7 +68,7 @@ namespace Nuclex.Support.Scheduling {
} }
/// <summary>Initializes a new queue operation</summary> /// <summary>Initializes a new queue operation</summary>
private QueueOperation() { private OperationQueue() {
this.asyncOperationEndedDelegate = new EventHandler(asyncOperationEnded); this.asyncOperationEndedDelegate = new EventHandler(asyncOperationEnded);
this.asyncOperationProgressChangedDelegate = new EventHandler<ProgressReportEventArgs>( this.asyncOperationProgressChangedDelegate = new EventHandler<ProgressReportEventArgs>(
asyncOperationProgressChanged asyncOperationProgressChanged
@ -99,7 +99,7 @@ namespace Nuclex.Support.Scheduling {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this waitable about the achieved progress.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(float progress) { protected virtual void OnAsyncProgressChanged(float progress) {
OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
@ -108,10 +108,10 @@ namespace Nuclex.Support.Scheduling {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this waitable about the achieved progress.
/// Allows for classes derived from the Progression class to easily provide /// Allows for classes derived from the Waitable class to easily provide
/// a custom event arguments class that has been derived from the /// a custom event arguments class that has been derived from the
/// Progression's ProgressUpdateEventArgs class. /// waitable's ProgressUpdateEventArgs class.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged; EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged;

View File

@ -23,33 +23,33 @@ using System.Collections.Generic;
namespace Nuclex.Support.Tracking { namespace Nuclex.Support.Tracking {
/// <summary>Progression being observed by another class</summary> /// <summary>Waitable being observed by another object</summary>
/// <typeparam name="ProgressionType"> /// <typeparam name="WaitableType">
/// Type of the progression that is being observed /// Type of the waitable that is being observed
/// </typeparam> /// </typeparam>
internal class ObservedWeightedWaitable<ProgressionType> : IDisposable internal class ObservedWeightedWaitable<WaitableType> : IDisposable
where ProgressionType : Waitable { where WaitableType : Waitable {
/// <summary>Delegate for reporting progress updates</summary> /// <summary>Delegate for reporting progress updates</summary>
public delegate void ReportDelegate(); public delegate void ReportDelegate();
/// <summary>Initializes a new observed progression</summary> /// <summary>Initializes a new observed waitable</summary>
/// <param name="weightedProgression">Weighted progression being observed</param> /// <param name="weightedWaitable">Weighted waitable being observed</param>
/// <param name="progressUpdateCallback"> /// <param name="progressUpdateCallback">
/// Callback to invoke when the progression's progress changes /// Callback to invoke when the waitable's progress changes
/// </param> /// </param>
/// <param name="endedCallback"> /// <param name="endedCallback">
/// Callback to invoke when the progression has ended /// Callback to invoke when the waitable has ended
/// </param> /// </param>
internal ObservedWeightedWaitable( internal ObservedWeightedWaitable(
WeightedWaitable<ProgressionType> weightedProgression, WeightedWaitable<WaitableType> weightedWaitable,
ReportDelegate progressUpdateCallback, ReportDelegate progressUpdateCallback,
ReportDelegate endedCallback ReportDelegate endedCallback
) { ) {
this.weightedProgression = weightedProgression; this.weightedWaitable = weightedWaitable;
// See if this progression has already ended (initial check for performance) // See if this waitable has already ended (initial check for performance)
if(weightedProgression.Waitable.Ended) { if(weightedWaitable.Waitable.Ended) {
this.progress = 1.0f; this.progress = 1.0f;
@ -58,18 +58,18 @@ namespace Nuclex.Support.Tracking {
this.endedCallback = endedCallback; this.endedCallback = endedCallback;
this.progressUpdateCallback = progressUpdateCallback; this.progressUpdateCallback = progressUpdateCallback;
this.weightedProgression.Waitable.AsyncEnded += this.weightedWaitable.Waitable.AsyncEnded +=
new EventHandler(asyncEnded); new EventHandler(asyncEnded);
// Check whether this progression might have ended before we were able to // Check whether this waitable might have ended before we were able to
// attach ourselfes to its event. If so, don't bother registering to the // attach ourselfes to its event. If so, don't bother registering to the
// other event and (important) set our progress to 1.0 because, since we // other event and (important) set our progress to 1.0 because, since we
// might not have gotten the 'Ended' event, it might otherwise stay at 0.0 // might not have gotten the 'Ended' event, it might otherwise stay at 0.0
// even though the progression is in the 'Ended' state. // even though the waitable is in the 'Ended' state.
if(weightedProgression.Waitable.Ended) { if(weightedWaitable.Waitable.Ended) {
this.progress = 1.0f; this.progress = 1.0f;
} else { } else {
this.progressReporter = this.weightedProgression.Waitable as IProgressReporter; this.progressReporter = this.weightedWaitable.Waitable as IProgressReporter;
if(this.progressReporter != null) { if(this.progressReporter != null) {
this.asyncProgressChangedEventHandler = new EventHandler<ProgressReportEventArgs>( this.asyncProgressChangedEventHandler = new EventHandler<ProgressReportEventArgs>(
@ -89,18 +89,18 @@ namespace Nuclex.Support.Tracking {
asyncDisconnectEvents(); asyncDisconnectEvents();
} }
/// <summary>Weighted progression being observed</summary> /// <summary>Weighted waitable being observed</summary>
public WeightedWaitable<ProgressionType> WeightedWaitable { public WeightedWaitable<WaitableType> WeightedWaitable {
get { return this.weightedProgression; } get { return this.weightedWaitable; }
} }
/// <summary>Amount of progress this progression has achieved so far</summary> /// <summary>Amount of progress this waitable has achieved so far</summary>
public float Progress { public float Progress {
get { return this.progress; } get { return this.progress; }
} }
/// <summary>Called when the observed progression has ended</summary> /// <summary>Called when the observed waitable has ended</summary>
/// <param name="sender">Progression that has ended</param> /// <param name="sender">Waitable that has ended</param>
/// <param name="e">Not used</param> /// <param name="e">Not used</param>
private void asyncEnded(object sender, EventArgs e) { private void asyncEnded(object sender, EventArgs e) {
ReportDelegate endedCallback = this.endedCallback; ReportDelegate endedCallback = this.endedCallback;
@ -109,11 +109,11 @@ namespace Nuclex.Support.Tracking {
asyncDisconnectEvents(); // We don't need those anymore! asyncDisconnectEvents(); // We don't need those anymore!
// If the progress hasn't reached 1.0 yet, make a fake report so that even // If the progress hasn't reached 1.0 yet, make a fake report so that even
// when a progression doesn't report any progress at all, the set or queue // when a waitable doesn't report any progress at all, the set or queue
// owning us will have a percentage of progressions completed. // owning us will have a percentage of waitables completed.
// //
// There is the possibility of a race condition here, as a final progress // There is the possibility of a race condition here, as a final progress
// report could have been generated by a thread running the progression // report could have been generated by a thread running the waitable
// that was preempted by this thread. This would cause the progress to // 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. // jump to 1.0 and then back to whatever the waiting thread will report.
if(this.progress != 1.0f) { if(this.progress != 1.0f) {
@ -124,15 +124,15 @@ namespace Nuclex.Support.Tracking {
endedCallback(); endedCallback();
} }
/// <summary>Called when the progress of the observed progression changes</summary> /// <summary>Called when the progress of the observed waitable changes</summary>
/// <param name="sender">Progression whose progress has changed</param> /// <param name="sender">Waitable whose progress has changed</param>
/// <param name="e">Contains the updated progress</param> /// <param name="e">Contains the updated progress</param>
private void asyncProgressChanged(object sender, ProgressReportEventArgs e) { private void asyncProgressChanged(object sender, ProgressReportEventArgs e) {
this.progress = e.Progress; this.progress = e.Progress;
this.progressUpdateCallback(); this.progressUpdateCallback();
} }
/// <summary>Unsubscribes from all events of the observed progression</summary> /// <summary>Unsubscribes from all events of the observed waitable</summary>
private void asyncDisconnectEvents() { private void asyncDisconnectEvents() {
// Make use of the double check locking idiom to avoid the costly lock when // Make use of the double check locking idiom to avoid the costly lock when
@ -143,8 +143,7 @@ namespace Nuclex.Support.Tracking {
// is no risk of deadlock involved, so we don't need a fancy syncRoot! // is no risk of deadlock involved, so we don't need a fancy syncRoot!
lock(this) { lock(this) {
if(this.endedCallback != null) { if(this.endedCallback != null) {
this.weightedProgression.Waitable.AsyncEnded -= this.weightedWaitable.Waitable.AsyncEnded -= new EventHandler(asyncEnded);
new EventHandler(asyncEnded);
if(this.progressReporter != null) { if(this.progressReporter != null) {
this.progressReporter.AsyncProgressChanged -= this.progressReporter.AsyncProgressChanged -=
@ -163,13 +162,13 @@ namespace Nuclex.Support.Tracking {
} }
private EventHandler<ProgressReportEventArgs> asyncProgressChangedEventHandler; private EventHandler<ProgressReportEventArgs> asyncProgressChangedEventHandler;
/// <summary>The observed progression's progress reporting interface</summary> /// <summary>The observed waitable's progress reporting interface</summary>
private IProgressReporter progressReporter; private IProgressReporter progressReporter;
/// <summary>The weighted progression that is being observed</summary> /// <summary>The weighted wable that is being observed</summary>
private WeightedWaitable<ProgressionType> weightedProgression; private WeightedWaitable<WaitableType> weightedWaitable;
/// <summary>Callback to invoke when the progress updates</summary> /// <summary>Callback to invoke when the progress updates</summary>
private volatile ReportDelegate progressUpdateCallback; private volatile ReportDelegate progressUpdateCallback;
/// <summary>Callback to invoke when the progression ends</summary> /// <summary>Callback to invoke when the waitable ends</summary>
private volatile ReportDelegate endedCallback; private volatile ReportDelegate endedCallback;
/// <summary>Progress achieved so far</summary> /// <summary>Progress achieved so far</summary>
private volatile float progress; private volatile float progress;

View File

@ -27,36 +27,36 @@ using Nuclex.Support.Collections;
namespace Nuclex.Support.Tracking { namespace Nuclex.Support.Tracking {
/// <summary>Collection of progressions with a weighting value</summary> /// <summary>Collection of waitables with a weighting value</summary>
/// <typeparam name="ProgressionType">Type of progressions to manage</typeparam> /// <typeparam name="WaitableType">Type of waitables to manage</typeparam>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// This collection is exposed as a read-only collection to the user that /// This collection is exposed as a read-only collection to the user that
/// stores WeightedProgressions. Internally, it merely wraps a collection of /// stores WeightedWaitables. Internally, it merely wraps a collection of
/// an internal type used to keep track of the individual progression's /// an internal type used to keep track of the individual waitable's
/// progress in the SetProgression and QueueOperation classes. /// progress in the WaitableSet and OperationQueue classes.
/// </para> /// </para>
/// <para> /// <para>
/// It is read-only because the design requires a progression to only ever /// It is read-only because the design requires a waitable to only ever
/// finish once. If it was possible eg. to add items after a SetProgression /// finish once. If it was possible eg. to add items after a WaitableSet
/// had signalled itself as being finished, it would be moved into an /// had signalled itself as being finished, it would be moved into an
/// unfinished state again. Also, an empty SetProgression is, by definition, /// unfinished state again. Also, an empty WaitableSet is, by definition,
/// finished (simply because there is no work to do) - unless the contents /// finished (simply because there is no work to do) - unless the contents
/// of set are passed to the SetProgression's constructor and never modified /// of set are passed to the WaitableSet's constructor and never modified
/// at all, the design would be violated as soon as ab instance of the /// at all, the design would be violated as soon as ab instance of the
/// SetProgression or QueueOperation classes was created. /// WaitableSet or OperationQueue classes was created.
/// </para> /// </para>
/// </remarks> /// </remarks>
internal class WeightedProgressionWrapperCollection<ProgressionType> : internal class WeightedWaitableWrapperCollection<WaitableType> :
TransformingReadOnlyCollection< TransformingReadOnlyCollection<
ObservedWeightedWaitable<ProgressionType>, WeightedWaitable<ProgressionType> ObservedWeightedWaitable<WaitableType>, WeightedWaitable<WaitableType>
> >
where ProgressionType : Waitable { where WaitableType : Waitable {
/// <summary>Initializes a new weighted progression collection wrapper</summary> /// <summary>Initializes a new weighted waitable collection wrapper</summary>
/// <param name="items">Items to be exposed as weighted progressions</param> /// <param name="items">Items to be exposed as weighted waitables</param>
internal WeightedProgressionWrapperCollection( internal WeightedWaitableWrapperCollection(
IList<ObservedWeightedWaitable<ProgressionType>> items IList<ObservedWeightedWaitable<WaitableType>> items
) )
: base(items) { } : base(items) { }
@ -69,8 +69,8 @@ namespace Nuclex.Support.Tracking {
/// be called frequently, because the TransformingReadOnlyCollection does /// be called frequently, because the TransformingReadOnlyCollection does
/// not cache otherwise store the transformed items. /// not cache otherwise store the transformed items.
/// </remarks> /// </remarks>
protected override WeightedWaitable<ProgressionType> Transform( protected override WeightedWaitable<WaitableType> Transform(
ObservedWeightedWaitable<ProgressionType> item ObservedWeightedWaitable<WaitableType> item
) { ) {
return item.WeightedWaitable; return item.WeightedWaitable;
} }

View File

@ -29,28 +29,28 @@ using NMock2;
namespace Nuclex.Support.Tracking { namespace Nuclex.Support.Tracking {
/// <summary>Unit Test for the progression tracker class</summary> /// <summary>Unit Test for the progress tracker class</summary>
[TestFixture] [TestFixture]
public class ProgressionTrackerTest { public class ProgressTrackerTest {
#region interface IProgressionTrackerSubscriber #region interface IProgressTrackerSubscriber
/// <summary>Interface used to test the progression tracker.</summary> /// <summary>Interface used to test the progress tracker</summary>
public interface IProgressionTrackerSubscriber { public interface IProgressTrackerSubscriber {
/// <summary>Called when the progression tracker's progress changes</summary> /// <summary>Called when the tracked progress changes</summary>
/// <param name="sender">Progression tracker whose progress has changed</param> /// <param name="sender">Progress tracker whose progress has changed</param>
/// <param name="e">Contains the new progress achieved</param> /// <param name="e">Contains the new progress achieved</param>
void ProgressChanged(object sender, ProgressReportEventArgs e); void ProgressChanged(object sender, ProgressReportEventArgs e);
/// <summary>Called when the progression tracker's idle state changes</summary> /// <summary>Called when the progress tracker's idle state changes</summary>
/// <param name="sender">Progression tracker whose idle state has changed</param> /// <param name="sender">Progress tracker whose idle state has changed</param>
/// <param name="e">Contains the new idle state of the tracker</param> /// <param name="e">Contains the new idle state of the tracker</param>
void IdleStateChanged(object sender, IdleStateEventArgs e); void IdleStateChanged(object sender, IdleStateEventArgs e);
} }
#endregion // interface IProgressionTrackerSubscriber #endregion // interface IProgressTrackerSubscriber
#region class ProgressUpdateEventArgsMatcher #region class ProgressUpdateEventArgsMatcher
@ -91,23 +91,21 @@ namespace Nuclex.Support.Tracking {
#endregion // class ProgressUpdateEventArgsMatcher #endregion // class ProgressUpdateEventArgsMatcher
#region class TestProgression #region class TestWaitable
/// <summary>Progression used for testing in this unit test</summary> /// <summary>Waitable used for testing in this unit test</summary>
private class TestProgression : Waitable, IProgressReporter { private class TestWaitable : Waitable, IProgressReporter {
/// <summary>will be triggered to report when progress has been achieved</summary> /// <summary>will be triggered to report when progress has been achieved</summary>
public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged; public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged;
/// <summary>Changes the testing progression's indicated progress</summary> /// <summary>Changes the testing waitable's indicated progress</summary>
/// <param name="progress"> /// <param name="progress">New progress to be reported by the testing waitable</param>
/// New progress to be reported by the testing progression
/// </param>
public void ChangeProgress(float progress) { public void ChangeProgress(float progress) {
OnAsyncProgressChanged(progress); OnAsyncProgressChanged(progress);
} }
/// <summary>Transitions the progression into the ended state</summary> /// <summary>Transitions the waitable into the ended state</summary>
public void End() { public void End() {
OnAsyncEnded(); OnAsyncEnded();
} }
@ -115,7 +113,7 @@ namespace Nuclex.Support.Tracking {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this waitable about the achieved progress.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(float progress) { protected virtual void OnAsyncProgressChanged(float progress) {
OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
@ -124,10 +122,10 @@ namespace Nuclex.Support.Tracking {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this waitable about the achieved progress.
/// Allows for classes derived from the Progression class to easily provide /// Allows for classes derived from the Waitable class to easily provide
/// a custom event arguments class that has been derived from the /// a custom event arguments class that has been derived from the
/// Progression's ProgressUpdateEventArgs class. /// waitable's ProgressUpdateEventArgs class.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged; EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged;
@ -137,7 +135,7 @@ namespace Nuclex.Support.Tracking {
} }
#endregion // class TestProgression #endregion // class TestWiatable
/// <summary>Initialization routine executed before each test is run</summary> /// <summary>Initialization routine executed before each test is run</summary>
[SetUp] [SetUp]
@ -150,7 +148,7 @@ namespace Nuclex.Support.Tracking {
public void TestSummedProgress() { public void TestSummedProgress() {
ProgressTracker tracker = new ProgressTracker(); ProgressTracker tracker = new ProgressTracker();
IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker); IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
Expect.Once.On(mockedSubscriber). Expect.Once.On(mockedSubscriber).
Method("IdleStateChanged"). Method("IdleStateChanged").
@ -165,9 +163,9 @@ namespace Nuclex.Support.Tracking {
} }
); );
TestProgression test1 = new TestProgression(); TestWaitable test1 = new TestWaitable();
tracker.Track(test1); tracker.Track(test1);
TestProgression test2 = new TestProgression(); TestWaitable test2 = new TestWaitable();
tracker.Track(test2); tracker.Track(test2);
Expect.Once.On(mockedSubscriber). Expect.Once.On(mockedSubscriber).
@ -185,18 +183,18 @@ namespace Nuclex.Support.Tracking {
} }
/// <summary> /// <summary>
/// Validates that the tracker only removes progressions when the whole /// Validates that the tracker only removes waitables when the whole
/// tracking list has reached the 'ended' state. /// tracking list has reached the 'ended' state.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// If the tracker would remove ended progressions right when they finished, /// If the tracker would remove ended waitables right when they finished,
/// the total progress would jump back each time. This is unwanted, of course. /// the total progress would jump back each time. This is unwanted, of course.
/// </remarks> /// </remarks>
[Test] [Test]
public void TestDelayedRemoval() { public void TestDelayedRemoval() {
ProgressTracker tracker = new ProgressTracker(); ProgressTracker tracker = new ProgressTracker();
IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker); IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
Expect.Once.On(mockedSubscriber). Expect.Once.On(mockedSubscriber).
Method("IdleStateChanged"). Method("IdleStateChanged").
@ -211,9 +209,9 @@ namespace Nuclex.Support.Tracking {
} }
); );
TestProgression test1 = new TestProgression(); TestWaitable test1 = new TestWaitable();
tracker.Track(test1); tracker.Track(test1);
TestProgression test2 = new TestProgression(); TestWaitable test2 = new TestWaitable();
tracker.Track(test2); tracker.Track(test2);
Expect.Once.On(mockedSubscriber). Expect.Once.On(mockedSubscriber).
@ -236,8 +234,8 @@ namespace Nuclex.Support.Tracking {
} }
); );
// Total progress should be 0.75 after this call (one progression at 1.0, // Total progress should be 0.75 after this call (one waitable at 1.0,
// the other one at 0.5). If the second progression would be removed, // the other one at 0.5). If the second waitable would be removed,
// the progress would jump to 0.5 instead. // the progress would jump to 0.5 instead.
test2.End(); test2.End();
@ -260,14 +258,14 @@ namespace Nuclex.Support.Tracking {
} }
/// <summary> /// <summary>
/// Validates that the tracker behaves correctly if it is fed with progressions /// Validates that the tracker behaves correctly if it is fed with waitables
/// that have already ended. /// that have already ended.
/// </summary> /// </summary>
[Test] [Test]
public void TestSoleEndedProgression() { public void TestSoleEndedWaitable() {
ProgressTracker tracker = new ProgressTracker(); ProgressTracker tracker = new ProgressTracker();
IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker); IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
tracker.Track(Waitable.EndedDummy); tracker.Track(Waitable.EndedDummy);
@ -275,14 +273,14 @@ namespace Nuclex.Support.Tracking {
} }
/// <summary> /// <summary>
/// Validates that the tracker behaves correctly if it is fed with progressions /// Validates that the tracker behaves correctly if it is fed with waitables
/// that have already ended in addition to progressions that are actively executing. /// that have already ended in addition to waitables that are actively executing.
/// </summary> /// </summary>
[Test] [Test]
public void TestEndedProgression() { public void TestEndedWaitable() {
ProgressTracker tracker = new ProgressTracker(); ProgressTracker tracker = new ProgressTracker();
IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker); IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
Expect.Once.On(mockedSubscriber). Expect.Once.On(mockedSubscriber).
Method("IdleStateChanged"). Method("IdleStateChanged").
@ -297,7 +295,7 @@ namespace Nuclex.Support.Tracking {
} }
); );
TestProgression test1 = new TestProgression(); TestWaitable test1 = new TestWaitable();
tracker.Track(test1); tracker.Track(test1);
Expect.Once.On(mockedSubscriber). Expect.Once.On(mockedSubscriber).
@ -337,7 +335,7 @@ namespace Nuclex.Support.Tracking {
public void TestProvokedDeadlock() { public void TestProvokedDeadlock() {
ProgressTracker tracker = new ProgressTracker(); ProgressTracker tracker = new ProgressTracker();
TestProgression test1 = new TestProgression(); TestWaitable test1 = new TestWaitable();
tracker.Track(test1); tracker.Track(test1);
tracker.AsyncIdleStateChanged += tracker.AsyncIdleStateChanged +=
@ -351,9 +349,9 @@ namespace Nuclex.Support.Tracking {
/// <summary>Mocks a subscriber for the events of a tracker</summary> /// <summary>Mocks a subscriber for the events of a tracker</summary>
/// <param name="tracker">Tracker to mock an event subscriber for</param> /// <param name="tracker">Tracker to mock an event subscriber for</param>
/// <returns>The mocked event subscriber</returns> /// <returns>The mocked event subscriber</returns>
private IProgressionTrackerSubscriber mockSubscriber(ProgressTracker tracker) { private IProgressTrackerSubscriber mockSubscriber(ProgressTracker tracker) {
IProgressionTrackerSubscriber mockedSubscriber = IProgressTrackerSubscriber mockedSubscriber =
this.mockery.NewMock<IProgressionTrackerSubscriber>(); this.mockery.NewMock<IProgressTrackerSubscriber>();
tracker.AsyncIdleStateChanged += tracker.AsyncIdleStateChanged +=
new EventHandler<IdleStateEventArgs>(mockedSubscriber.IdleStateChanged); new EventHandler<IdleStateEventArgs>(mockedSubscriber.IdleStateChanged);

View File

@ -23,18 +23,18 @@ using System.Collections.Generic;
namespace Nuclex.Support.Tracking { namespace Nuclex.Support.Tracking {
/// <summary>Extended type of progression that is able to fail</summary> /// <summary>Asynchronous request running in the background</summary>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// If the background process fails, the exception that caused it to fail is /// If the background process fails, the exception that caused it to fail is
/// communicated to all parties waiting on the progression through the /// communicated to all parties waiting on the Request through the Join()
/// Exception property. Implementers should place their code in try..catch /// method. Implementers should store any errors occuring in the asynchronous
/// blocks and call SetException() to temporarily store the exception for /// parts of their code in a try..catch block (or avoid throwing and just
/// retrieval by the caller(s). /// store a new exception) and re-throw them when in ReraiseExceptions()
/// </para> /// </para>
/// <para> /// <para>
/// As with all progressions, the interface contract still requires you to call /// Like in the Waitable class, the contract requires you to always call
/// OnAsyncEnded(), no matter what the outcome of your background operation is. /// OnAsyncEnded(), no matter what the outcome of your operation is.
/// </para> /// </para>
/// </remarks> /// </remarks>
public abstract class Request : Waitable { public abstract class Request : Waitable {
@ -65,6 +65,13 @@ namespace Nuclex.Support.Tracking {
#endregion // EndedDummyRequest #endregion // EndedDummyRequest
/// <summary>Succeeded dummy request</summary>
/// <remarks>
/// Use to indicate success if the request has already been completed at
/// the time you are asked to perform it.
/// </remarks>
public static readonly Request SucceededDummy = new EndedDummyRequest();
/// <summary>Creates a new failed dummy request</summary> /// <summary>Creates a new failed dummy request</summary>
/// <param name="exception">Exception that supposedly caused the request to fail</param> /// <param name="exception">Exception that supposedly caused the request to fail</param>
/// <returns> /// <returns>
@ -83,7 +90,7 @@ namespace Nuclex.Support.Tracking {
/// </remarks> /// </remarks>
public virtual void Join() { public virtual void Join() {
// If the progression itself hasn't ended yet, block the caller until it has. // 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 // 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 // on-the-fly only when it is requested, we can avoid the WaitHandle creation in
// case the request is already finished! // case the request is already finished!
@ -109,13 +116,36 @@ namespace Nuclex.Support.Tracking {
/// </typeparam> /// </typeparam>
public abstract class Request<ResultType> : Request { public abstract class Request<ResultType> : Request {
#region class EndedDummyRequest #region class SucceededDummyRequest
/// <summary>Dummy request that is always in the ended state</summary> /// <summary>Succeeded dummy request that is always in the ended state</summary>
private class EndedDummyRequest : Request<ResultType> { private class SucceededDummyRequest : Request<ResultType> {
/// <summary>Creates a new failed dummy request</summary>
/// <param name="result">Result to return to the request's caller</param>
public SucceededDummyRequest(ResultType result) {
this.result = result;
OnAsyncEnded();
}
/// <summary>
/// Allows the specific request implementation to re-throw an exception if
/// the background process finished unsuccessfully
/// </summary>
protected override ResultType GatherResults() {
return this.result;
}
/// <summary>Results the succeede dummy request will provide to the caller</summary>
private ResultType result;
}
#endregion // SucceededDummyRequest
#region class FailedDummyRequest
/// <summary>Failed dummy request that is always in the ended state</summary>
private class FailedDummyRequest : Request<ResultType> {
/// <summary>Creates a new failed dummy request</summary> /// <summary>Creates a new failed dummy request</summary>
/// <param name="exception">Exception that caused the dummy to fail</param> /// <param name="exception">Exception that caused the dummy to fail</param>
public EndedDummyRequest(Exception exception) { public FailedDummyRequest(Exception exception) {
this.exception = exception; this.exception = exception;
OnAsyncEnded(); OnAsyncEnded();
} }
@ -130,15 +160,24 @@ namespace Nuclex.Support.Tracking {
private Exception exception; private Exception exception;
} }
#endregion // EndedDummyRequest #endregion // FailedDummyRequest
/// <summary>Creates a new failed dummy request</summary>
/// <param name="result">Result to provide to the caller</param>
/// <returns>
/// A succeeded request that returns the provided result to the caller
/// </returns>
public static Request<ResultType> CreateSucceededDummy(ResultType result) {
return new SucceededDummyRequest(result);
}
/// <summary>Creates a new failed dummy request</summary> /// <summary>Creates a new failed dummy request</summary>
/// <param name="exception">Exception that supposedly caused the request to fail</param> /// <param name="exception">Exception that supposedly caused the request to fail</param>
/// <returns> /// <returns>
/// A failed request that reports the provided exception as cause for its failure /// A failed request that reports the provided exception as cause for its failure
/// </returns> /// </returns>
public static new Request CreateFailedDummy(Exception exception) { public static new Request<ResultType> CreateFailedDummy(Exception exception) {
return new EndedDummyRequest(exception); return new FailedDummyRequest(exception);
} }
/// <summary>Waits for the background operation to end</summary> /// <summary>Waits for the background operation to end</summary>

View File

@ -62,7 +62,7 @@ namespace Nuclex.Support.Tracking {
/// <summary>A dummy waitable that's always in the 'ended' state</summary> /// <summary>A dummy waitable that's always in the 'ended' state</summary>
/// <remarks> /// <remarks>
/// Useful if an operation is already complete when it's being asked for or /// Useful if an operation is already complete when it's being asked for or
/// when a progression that's lazily created is accessed after the original /// when a waitable that's lazily created is accessed after the original
/// operation has ended already. /// operation has ended already.
/// </remarks> /// </remarks>
public static readonly Waitable EndedDummy = new EndedDummyWaitable(); public static readonly Waitable EndedDummy = new EndedDummyWaitable();
@ -186,14 +186,14 @@ namespace Nuclex.Support.Tracking {
/// </para> /// </para>
/// <para> /// <para>
/// Calling this method is mandatory. Implementers need to take care that /// Calling this method is mandatory. Implementers need to take care that
/// the OnAsyncEnded() method is called on any instance of Progression that's /// the OnAsyncEnded() method is called on any instance of Waitable that's
/// being created. This method also must not be called more than once. /// being created. This method also must not be called more than once.
/// </para> /// </para>
/// </remarks> /// </remarks>
protected virtual void OnAsyncEnded() { protected virtual void OnAsyncEnded() {
// Make sure the progression is not ended more than once. By guaranteeing that // Make sure the waitable is not ended more than once. By guaranteeing that
// a progression can only be ended once, we allow users of this class to // a waitable can only be ended once, we allow users of this class to
// skip some safeguards against notifications arriving twice. // skip some safeguards against notifications arriving twice.
lock(this) { lock(this) {
@ -202,7 +202,7 @@ namespace Nuclex.Support.Tracking {
// to waste any effort optimizing the speed at which an implementation fault // to waste any effort optimizing the speed at which an implementation fault
// will be noticed. // will be noticed.
if(this.ended) if(this.ended)
throw new InvalidOperationException("The progression has already been ended"); throw new InvalidOperationException("The Waitable has already been ended");
this.ended = true; this.ended = true;
@ -236,7 +236,7 @@ namespace Nuclex.Support.Tracking {
protected volatile List<EventHandler> endedEventSubscribers; protected volatile List<EventHandler> endedEventSubscribers;
/// <summary>Whether the operation has completed yet</summary> /// <summary>Whether the operation has completed yet</summary>
protected volatile bool ended; protected volatile bool ended;
/// <summary>Event that will be set when the progression is completed</summary> /// <summary>Event that will be set when the waitable is completed</summary>
/// <remarks> /// <remarks>
/// This event is will only be created when it is specifically asked for using /// This event is will only be created when it is specifically asked for using
/// the WaitHandle property. /// the WaitHandle property.

View File

@ -29,28 +29,28 @@ using NMock2;
namespace Nuclex.Support.Tracking { namespace Nuclex.Support.Tracking {
/// <summary>Unit Test for the progression set class</summary> /// <summary>Unit Test for the waitable group class</summary>
[TestFixture] [TestFixture]
public class SetProgressionTest { public class WaitableGroupTest {
#region interface ISetProgressionSubscriber #region interface IWaitableGroupSubscriber
/// <summary>Interface used to test the set progression.</summary> /// <summary>Interface used to test the set waitable.</summary>
public interface ISetProgressionSubscriber { public interface IWaitableGroupSubscriber {
/// <summary>Called when the set progression's progress changes</summary> /// <summary>Called when the set waitable's progress changes</summary>
/// <param name="sender">Set progression whose progress has changed</param> /// <param name="sender">Waitable group whose progress has changed</param>
/// <param name="e">Contains the new progress achieved</param> /// <param name="e">Contains the new progress achieved</param>
void ProgressChanged(object sender, ProgressReportEventArgs e); void ProgressChanged(object sender, ProgressReportEventArgs e);
/// <summary>Called when the set progression has ended</summary> /// <summary>Called when the set waitable has ended</summary>
/// <param name="sender">Set progression that as ended</param> /// <param name="sender">Waitable group that as ended</param>
/// <param name="e">Not used</param> /// <param name="e">Not used</param>
void Ended(object sender, EventArgs e); void Ended(object sender, EventArgs e);
} }
#endregion // interface ISetProgressionSubscriber #endregion // interface IWaitableGroupSubscriber
#region class ProgressUpdateEventArgsMatcher #region class ProgressUpdateEventArgsMatcher
@ -93,21 +93,21 @@ namespace Nuclex.Support.Tracking {
#region class TestWaitable #region class TestWaitable
/// <summary>Progression used for testing in this unit test</summary> /// <summary>Waitable used for testing in this unit test</summary>
private class TestWaitable : Waitable, IProgressReporter { private class TestWaitable : Waitable, IProgressReporter {
/// <summary>will be triggered to report when progress has been achieved</summary> /// <summary>will be triggered to report when progress has been achieved</summary>
public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged; public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged;
/// <summary>Changes the testing progression's indicated progress</summary> /// <summary>Changes the testing waitable's indicated progress</summary>
/// <param name="progress"> /// <param name="progress">
/// New progress to be reported by the testing progression /// New progress to be reported by the testing waitable
/// </param> /// </param>
public void ChangeProgress(float progress) { public void ChangeProgress(float progress) {
OnAsyncProgressChanged(progress); OnAsyncProgressChanged(progress);
} }
/// <summary>Transitions the progression into the ended state</summary> /// <summary>Transitions the waitable into the ended state</summary>
public void End() { public void End() {
OnAsyncEnded(); OnAsyncEnded();
} }
@ -115,7 +115,7 @@ namespace Nuclex.Support.Tracking {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this waitable about the achieved progress.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(float progress) { protected virtual void OnAsyncProgressChanged(float progress) {
OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
@ -124,10 +124,10 @@ namespace Nuclex.Support.Tracking {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this waitable about the achieved progress.
/// Allows for classes derived from the Progression class to easily provide /// Allows for classes derived from the Waitable class to easily provide
/// a custom event arguments class that has been derived from the /// a custom event arguments class that has been derived from the
/// Progression's ProgressUpdateEventArgs class. /// Waitable's ProgressUpdateEventArgs class.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged; EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged;
@ -145,79 +145,79 @@ namespace Nuclex.Support.Tracking {
this.mockery = new Mockery(); this.mockery = new Mockery();
} }
/// <summary>Validates that the set progression properly sums the progress</summary> /// <summary>Validates that the set waitable properly sums the progress</summary>
[Test] [Test]
public void TestSummedProgress() { public void TestSummedProgress() {
SetProgression<TestWaitable> testSetProgression = WaitableGroup<TestWaitable> testWaitableGroup =
new SetProgression<TestWaitable>( new WaitableGroup<TestWaitable>(
new TestWaitable[] { new TestWaitable(), new TestWaitable() } new TestWaitable[] { new TestWaitable(), new TestWaitable() }
); );
ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression); IWaitableGroupSubscriber mockedSubscriber = mockSubscriber(testWaitableGroup);
Expect.Once.On(mockedSubscriber). Expect.Once.On(mockedSubscriber).
Method("ProgressChanged"). Method("ProgressChanged").
With( With(
new Matcher[] { new Matcher[] {
new NMock2.Matchers.TypeMatcher(typeof(SetProgression<TestWaitable>)), new NMock2.Matchers.TypeMatcher(typeof(WaitableGroup<TestWaitable>)),
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f)) new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f))
} }
); );
testSetProgression.Children[0].Waitable.ChangeProgress(0.5f); testWaitableGroup.Children[0].Waitable.ChangeProgress(0.5f);
this.mockery.VerifyAllExpectationsHaveBeenMet(); this.mockery.VerifyAllExpectationsHaveBeenMet();
} }
/// <summary>Validates that the set progression respects the weights</summary> /// <summary>Validates that the waitable group respects the weights</summary>
[Test] [Test]
public void TestWeightedSummedProgress() { public void TestWeightedSummedProgress() {
SetProgression<TestWaitable> testSetProgression = WaitableGroup<TestWaitable> testWaitableGroup =
new SetProgression<TestWaitable>( new WaitableGroup<TestWaitable>(
new WeightedWaitable<TestWaitable>[] { new WeightedWaitable<TestWaitable>[] {
new WeightedWaitable<TestWaitable>(new TestWaitable(), 1.0f), new WeightedWaitable<TestWaitable>(new TestWaitable(), 1.0f),
new WeightedWaitable<TestWaitable>(new TestWaitable(), 2.0f) new WeightedWaitable<TestWaitable>(new TestWaitable(), 2.0f)
} }
); );
ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression); IWaitableGroupSubscriber mockedSubscriber = mockSubscriber(testWaitableGroup);
Expect.Once.On(mockedSubscriber). Expect.Once.On(mockedSubscriber).
Method("ProgressChanged"). Method("ProgressChanged").
With( With(
new Matcher[] { new Matcher[] {
new NMock2.Matchers.TypeMatcher(typeof(SetProgression<TestWaitable>)), new NMock2.Matchers.TypeMatcher(typeof(WaitableGroup<TestWaitable>)),
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f / 3.0f)) new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f / 3.0f))
} }
); );
testSetProgression.Children[0].Waitable.ChangeProgress(0.5f); testWaitableGroup.Children[0].Waitable.ChangeProgress(0.5f);
Expect.Once.On(mockedSubscriber). Expect.Once.On(mockedSubscriber).
Method("ProgressChanged"). Method("ProgressChanged").
With( With(
new Matcher[] { new Matcher[] {
new NMock2.Matchers.TypeMatcher(typeof(SetProgression<TestWaitable>)), new NMock2.Matchers.TypeMatcher(typeof(WaitableGroup<TestWaitable>)),
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f)) new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f))
} }
); );
testSetProgression.Children[1].Waitable.ChangeProgress(0.5f); testWaitableGroup.Children[1].Waitable.ChangeProgress(0.5f);
this.mockery.VerifyAllExpectationsHaveBeenMet(); this.mockery.VerifyAllExpectationsHaveBeenMet();
} }
/// <summary> /// <summary>
/// Validates that the ended event is triggered when the last progression ends /// Validates that the ended event is triggered when the last waitable ends
/// </summary> /// </summary>
[Test] [Test]
public void TestEndedEvent() { public void TestEndedEvent() {
SetProgression<TestWaitable> testSetProgression = WaitableGroup<TestWaitable> testWaitableGroup =
new SetProgression<TestWaitable>( new WaitableGroup<TestWaitable>(
new TestWaitable[] { new TestWaitable(), new TestWaitable() } new TestWaitable[] { new TestWaitable(), new TestWaitable() }
); );
ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression); IWaitableGroupSubscriber mockedSubscriber = mockSubscriber(testWaitableGroup);
Expect.Exactly(2).On(mockedSubscriber). Expect.Exactly(2).On(mockedSubscriber).
Method("ProgressChanged"). Method("ProgressChanged").
@ -227,21 +227,21 @@ namespace Nuclex.Support.Tracking {
Method("Ended"). Method("Ended").
WithAnyArguments(); WithAnyArguments();
testSetProgression.Children[0].Waitable.End(); testWaitableGroup.Children[0].Waitable.End();
testSetProgression.Children[1].Waitable.End(); testWaitableGroup.Children[1].Waitable.End();
this.mockery.VerifyAllExpectationsHaveBeenMet(); this.mockery.VerifyAllExpectationsHaveBeenMet();
} }
/// <summary>Mocks a subscriber for the events of a progression</summary> /// <summary>Mocks a subscriber for the events of a waitable</summary>
/// <param name="progression">Progression to mock an event subscriber for</param> /// <param name="waitable">Waitable to mock an event subscriber for</param>
/// <returns>The mocked event subscriber</returns> /// <returns>The mocked event subscriber</returns>
private ISetProgressionSubscriber mockSubscriber(Waitable progression) { private IWaitableGroupSubscriber mockSubscriber(Waitable waitable) {
ISetProgressionSubscriber mockedSubscriber = IWaitableGroupSubscriber mockedSubscriber =
this.mockery.NewMock<ISetProgressionSubscriber>(); this.mockery.NewMock<IWaitableGroupSubscriber>();
progression.AsyncEnded += new EventHandler(mockedSubscriber.Ended); waitable.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
(progression as IProgressReporter).AsyncProgressChanged += (waitable as IProgressReporter).AsyncProgressChanged +=
new EventHandler<ProgressReportEventArgs>(mockedSubscriber.ProgressChanged); new EventHandler<ProgressReportEventArgs>(mockedSubscriber.ProgressChanged);
return mockedSubscriber; return mockedSubscriber;

View File

@ -26,66 +26,66 @@ using Nuclex.Support.Collections;
namespace Nuclex.Support.Tracking { namespace Nuclex.Support.Tracking {
/// <summary>Forms a single progression from a set of progressions</summary> /// <summary>Forms a single waitable from a group of waitables</summary>
/// <typeparam name="ProgressionType">Type of progressions to manage as a set</typeparam> /// <typeparam name="WaitableType">Type of waitables to manage as a set</typeparam>
public class SetProgression<ProgressionType> : Waitable, IDisposable, IProgressReporter public class WaitableGroup<WaitableType> : Waitable, IDisposable, IProgressReporter
where ProgressionType : Waitable { where WaitableType : Waitable {
/// <summary>will be triggered to report when progress has been achieved</summary> /// <summary>will be triggered to report when progress has been achieved</summary>
public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged; public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged;
/// <summary>Initializes a new set progression</summary> /// <summary>Initializes a new waitable group</summary>
/// <param name="childs">Progressions to track with this set</param> /// <param name="childs">Waitables to track with this group</param>
/// <remarks> /// <remarks>
/// Uses a default weighting factor of 1.0 for all progressions. /// Uses a default weighting factor of 1.0 for all waitables.
/// </remarks> /// </remarks>
public SetProgression(IEnumerable<ProgressionType> childs) public WaitableGroup(IEnumerable<WaitableType> childs)
: this() { : this() {
// Construct a WeightedProgression with the default weight for each // Construct a WeightedWaitable with the default weight for each
// progression and wrap it in an ObservedProgression // waitable and wrap it in an ObservedWaitable
foreach(ProgressionType progression in childs) { foreach(WaitableType waitable in childs) {
this.children.Add( this.children.Add(
new ObservedWeightedWaitable<ProgressionType>( new ObservedWeightedWaitable<WaitableType>(
new WeightedWaitable<ProgressionType>(progression), new WeightedWaitable<WaitableType>(waitable),
new ObservedWeightedWaitable<ProgressionType>.ReportDelegate(asyncProgressUpdated), new ObservedWeightedWaitable<WaitableType>.ReportDelegate(asyncProgressUpdated),
new ObservedWeightedWaitable<ProgressionType>.ReportDelegate(asyncEnded) new ObservedWeightedWaitable<WaitableType>.ReportDelegate(asyncChildEnded)
) )
); );
} }
// Since all progressions have a weight of 1.0, the total weight is // Since all waitables have a weight of 1.0, the total weight is
// equal to the number of progressions in our list // equal to the number of waitables in our list
this.totalWeight = (float)this.children.Count; this.totalWeight = (float)this.children.Count;
} }
/// <summary>Initializes a new set progression</summary> /// <summary>Initializes a new waitable group</summary>
/// <param name="childs">Progressions to track with this set</param> /// <param name="childs">Waitables to track with this group</param>
public SetProgression( public WaitableGroup(
IEnumerable<WeightedWaitable<ProgressionType>> childs IEnumerable<WeightedWaitable<WaitableType>> childs
) )
: this() { : this() {
// Construct an ObservedProgression around each of the WeightedProgressions // Construct an ObservedWaitable around each of the WeightedWaitables
foreach(WeightedWaitable<ProgressionType> progression in childs) { foreach(WeightedWaitable<WaitableType> waitable in childs) {
this.children.Add( this.children.Add(
new ObservedWeightedWaitable<ProgressionType>( new ObservedWeightedWaitable<WaitableType>(
progression, waitable,
new ObservedWeightedWaitable<ProgressionType>.ReportDelegate(asyncProgressUpdated), new ObservedWeightedWaitable<WaitableType>.ReportDelegate(asyncProgressUpdated),
new ObservedWeightedWaitable<ProgressionType>.ReportDelegate(asyncEnded) new ObservedWeightedWaitable<WaitableType>.ReportDelegate(asyncChildEnded)
) )
); );
// Sum up the total weight // Sum up the total weight
this.totalWeight += progression.Weight; this.totalWeight += waitable.Weight;
} }
} }
/// <summary>Performs common initialization for the public constructors</summary> /// <summary>Performs common initialization for the public constructors</summary>
private SetProgression() { private WaitableGroup() {
this.children = new List<ObservedWeightedWaitable<ProgressionType>>(); this.children = new List<ObservedWeightedWaitable<WaitableType>>();
} }
/// <summary>Immediately releases all resources owned by the object</summary> /// <summary>Immediately releases all resources owned by the object</summary>
@ -93,8 +93,8 @@ namespace Nuclex.Support.Tracking {
if(this.children != null) { if(this.children != null) {
// Dispose all the observed progressions, disconnecting the events from the // Dispose all the observed waitables, disconnecting the events from the
// actual progressions so the GC can more easily collect this class // actual waitables so the GC can more easily collect this class
for(int index = 0; index < this.children.Count; ++index) for(int index = 0; index < this.children.Count; ++index)
this.children[index].Dispose(); this.children[index].Dispose();
@ -105,18 +105,18 @@ namespace Nuclex.Support.Tracking {
} }
/// <summary>Childs contained in the progression set</summary> /// <summary>Childs contained in the waitable set</summary>
public IList<WeightedWaitable<ProgressionType>> Children { public IList<WeightedWaitable<WaitableType>> Children {
get { get {
// The wrapper is constructed only when needed. Most of the time, users will // The wrapper is constructed only when needed. Most of the time, users will
// just create a SetProgression and monitor its progress without ever using // just create a waitable group and monitor its progress without ever using
// the Childs collection. // the Childs collection.
if(this.wrapper == null) { if(this.wrapper == null) {
// This doesn't need a lock because it's a stateless wrapper. // 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. // If it is constructed twice, then so be it, no problem at all.
this.wrapper = new WeightedProgressionWrapperCollection<ProgressionType>( this.wrapper = new WeightedWaitableWrapperCollection<WaitableType>(
this.children this.children
); );
@ -130,7 +130,7 @@ namespace Nuclex.Support.Tracking {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this waitables about the achieved progress.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(float progress) { protected virtual void OnAsyncProgressChanged(float progress) {
OnAsyncProgressChanged(new ProgressReportEventArgs(progress)); OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
@ -139,10 +139,10 @@ namespace Nuclex.Support.Tracking {
/// <summary>Fires the progress update event</summary> /// <summary>Fires the progress update event</summary>
/// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param> /// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param>
/// <remarks> /// <remarks>
/// Informs the observers of this progression about the achieved progress. /// Informs the observers of this waitable about the achieved progress.
/// Allows for classes derived from the Progression class to easily provide /// Allows for classes derived from the Waitable class to easily provide
/// a custom event arguments class that has been derived from the /// a custom event arguments class that has been derived from the
/// Progression's ProgressUpdateEventArgs class. /// waitable's ProgressUpdateEventArgs class.
/// </remarks> /// </remarks>
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) { protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged; EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged;
@ -151,13 +151,13 @@ namespace Nuclex.Support.Tracking {
} }
/// <summary> /// <summary>
/// Called when the progress of one of the observed progressions changes /// Called when the progress of one of the observed waitables changes
/// </summary> /// </summary>
private void asyncProgressUpdated() { private void asyncProgressUpdated() {
float totalProgress = 0.0f; float totalProgress = 0.0f;
// Calculate the sum of the progress reported by our child progressions, // Calculate the sum of the progress reported by our child waitables,
// scaled to the weight each progression has assigned to it. // scaled to the weight each waitable has assigned to it.
for(int index = 0; index < this.children.Count; ++index) { for(int index = 0; index < this.children.Count; ++index) {
totalProgress += totalProgress +=
this.children[index].Progress * this.children[index].WeightedWaitable.Weight; this.children[index].Progress * this.children[index].WeightedWaitable.Weight;
@ -172,29 +172,29 @@ namespace Nuclex.Support.Tracking {
} }
/// <summary> /// <summary>
/// Called when an observed progression ends /// Called when an observed waitable ends
/// </summary> /// </summary>
private void asyncEnded() { private void asyncChildEnded() {
// If there's still at least one progression going, don't report that // If there's still at least one waitable going, don't report that
// the SetProgression has finished yet. // the waitable group has finished yet.
for(int index = 0; index < this.children.Count; ++index) for(int index = 0; index < this.children.Count; ++index)
if(!this.children[index].WeightedWaitable.Waitable.Ended) if(!this.children[index].WeightedWaitable.Waitable.Ended)
return; return;
// All child progressions have ended, so the set has now ended as well // All child waitables have ended, so the set has now ended as well
OnAsyncEnded(); OnAsyncEnded();
} }
/// <summary>Progressions being managed in the set</summary> /// <summary>Waitables being managed in the set</summary>
private List<ObservedWeightedWaitable<ProgressionType>> children; private List<ObservedWeightedWaitable<WaitableType>> children;
/// <summary> /// <summary>
/// Wrapper collection for exposing the child progressions under the /// Wrapper collection for exposing the child waitables under the
/// WeightedProgression interface /// WeightedWaitable interface
/// </summary> /// </summary>
private volatile WeightedProgressionWrapperCollection<ProgressionType> wrapper; private volatile WeightedWaitableWrapperCollection<WaitableType> wrapper;
/// <summary>Summed weight of all progressions in the set</summary> /// <summary>Summed weight of all waitables in the set</summary>
private float totalWeight; private float totalWeight;
} }

View File

@ -23,36 +23,36 @@ using System.Collections.Generic;
namespace Nuclex.Support.Tracking { namespace Nuclex.Support.Tracking {
/// <summary>Progression with an associated weight for the total progress</summary> /// <summary>Waitable with an associated weight for the total progress</summary>
public class WeightedWaitable<ProgressionType> where ProgressionType : Waitable { public class WeightedWaitable<WaitableType> where WaitableType : Waitable {
/// <summary> /// <summary>
/// Initializes a new weighted progression with a default weight of 1.0 /// Initializes a new weighted waitable with a default weight of 1.0
/// </summary> /// </summary>
/// <param name="progression">Progression whose progress to monitor</param> /// <param name="waitable">Waitable whose progress to monitor</param>
public WeightedWaitable(ProgressionType progression) : this(progression, 1.0f) { } public WeightedWaitable(WaitableType waitable) : this(waitable, 1.0f) { }
/// <summary>Initializes a new weighted progression</summary> /// <summary>Initializes a new weighted waitable</summary>
/// <param name="progression">Progression whose progress to monitor</param> /// <param name="waitable">Waitable whose progress to monitor</param>
/// <param name="weight">Weighting of the progression's progress</param> /// <param name="weight">Weighting of the waitable's progress</param>
public WeightedWaitable(ProgressionType progression, float weight) { public WeightedWaitable(WaitableType waitable, float weight) {
this.progression = progression; this.waitable = waitable;
this.weight = weight; this.weight = weight;
} }
/// <summary>Progression being wrapped by this weighted progression</summary> /// <summary>Waitable being wrapped by this weighted waitable</summary>
public ProgressionType Waitable { public WaitableType Waitable {
get { return this.progression; } get { return this.waitable; }
} }
/// <summary>The contribution of this progression to the total progress</summary> /// <summary>The contribution of this waitable to the total progress</summary>
public float Weight { public float Weight {
get { return this.weight; } get { return this.weight; }
} }
/// <summary>Progression whose progress we're tracking</summary> /// <summary>Waitable whose progress we're tracking</summary>
private ProgressionType progression; private WaitableType waitable;
/// <summary>Weighting of this progression in the total progress</summary> /// <summary>Weighting of this waitable in the total progress</summary>
private float weight; private float weight;
} }