Renamed Waitable to Transaction (Progression -> Waitable -> Transaction -- might it be that this concept is no crispy enough and should be revisited? I think so!); increased test coverage for lots of classes in the tracking namespace, all but 3 are now 100% covered; eliminated redundant ProgressUpdate events from the ProgressTracker and adjusted unit tests so progress update events that re-report the current progress are optional; added a new idea for a replacement of the broken (quality-wise, at least) command line parser
git-svn-id: file:///srv/devel/repo-conversion/nusu@100 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
b39f8de155
commit
8c5f2d45f7
|
@ -118,6 +118,7 @@
|
||||||
<Compile Include="Source\Parsing\CommandLineParser.Test.cs">
|
<Compile Include="Source\Parsing\CommandLineParser.Test.cs">
|
||||||
<DependentUpon>CommandLineParser.cs</DependentUpon>
|
<DependentUpon>CommandLineParser.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Source\Parsing\CommandLine.cs" />
|
||||||
<Compile Include="Source\PathHelper.cs" />
|
<Compile Include="Source\PathHelper.cs" />
|
||||||
<Compile Include="Source\PathHelper.Test.cs">
|
<Compile Include="Source\PathHelper.Test.cs">
|
||||||
<DependentUpon>PathHelper.cs</DependentUpon>
|
<DependentUpon>PathHelper.cs</DependentUpon>
|
||||||
|
@ -176,23 +177,44 @@
|
||||||
<DependentUpon>StringSegment.cs</DependentUpon>
|
<DependentUpon>StringSegment.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Source\Tracking\IdleStateEventArgs.cs" />
|
<Compile Include="Source\Tracking\IdleStateEventArgs.cs" />
|
||||||
<Compile Include="Source\Tracking\Internal\ObservedWeightedWaitable.cs" />
|
<Compile Include="Source\Tracking\IdleStateEventArgs.Test.cs">
|
||||||
<Compile Include="Source\Tracking\Internal\WeightedWaitableWrapperCollection.cs" />
|
<DependentUpon>IdleStateEventArgs.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Source\Tracking\Internal\ObservedWeightedTransaction.cs" />
|
||||||
|
<Compile Include="Source\Tracking\Internal\ObservedWeightedTransaction.Test.cs">
|
||||||
|
<DependentUpon>ObservedWeightedTransaction.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Source\Tracking\Internal\WeightedTransactionWrapperCollection.cs" />
|
||||||
|
<Compile Include="Source\Tracking\Internal\WeightedTransactionWrapperCollection.Test.cs">
|
||||||
|
<DependentUpon>WeightedTransactionWrapperCollection.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Source\Tracking\IProgressReporter.cs" />
|
<Compile Include="Source\Tracking\IProgressReporter.cs" />
|
||||||
<Compile Include="Source\Tracking\IStatusReporter.cs" />
|
<Compile Include="Source\Tracking\IStatusReporter.cs" />
|
||||||
<Compile Include="Source\Tracking\ProgressReportEventArgs.cs" />
|
<Compile Include="Source\Tracking\ProgressReportEventArgs.cs" />
|
||||||
|
<Compile Include="Source\Tracking\ProgressReportEventArgs.Test.cs">
|
||||||
|
<DependentUpon>ProgressReportEventArgs.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Source\Tracking\ProgressTracker.cs" />
|
<Compile Include="Source\Tracking\ProgressTracker.cs" />
|
||||||
<Compile Include="Source\Tracking\ProgressTracker.Test.cs">
|
<Compile Include="Source\Tracking\ProgressTracker.Test.cs">
|
||||||
<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\WaitableGroup.cs" />
|
<Compile Include="Source\Tracking\StatusReportEventArgs.Test.cs">
|
||||||
<Compile Include="Source\Tracking\WaitableGroup.Test.cs">
|
<DependentUpon>StatusReportEventArgs.cs</DependentUpon>
|
||||||
<DependentUpon>WaitableGroup.cs</DependentUpon>
|
</Compile>
|
||||||
|
<Compile Include="Source\Tracking\Transaction.Test.cs">
|
||||||
|
<DependentUpon>Transaction.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Source\Tracking\TransactionGroup.cs" />
|
||||||
|
<Compile Include="Source\Tracking\TransactionGroup.Test.cs">
|
||||||
|
<DependentUpon>TransactionGroup.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\Transaction.cs" />
|
||||||
<Compile Include="Source\Tracking\WeightedWaitable.cs" />
|
<Compile Include="Source\Tracking\WeightedTransaction.cs" />
|
||||||
|
<Compile Include="Source\Tracking\WeightedTransaction.Test.cs">
|
||||||
|
<DependentUpon>WeightedTransaction.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Source\WeakReference.cs" />
|
<Compile Include="Source\WeakReference.cs" />
|
||||||
<Compile Include="Source\WeakReference.Test.cs">
|
<Compile Include="Source\WeakReference.Test.cs">
|
||||||
<DependentUpon>WeakReference.cs</DependentUpon>
|
<DependentUpon>WeakReference.cs</DependentUpon>
|
||||||
|
|
46
Source/Parsing/CommandLine.cs
Normal file
46
Source/Parsing/CommandLine.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Source.Parsing {
|
||||||
|
|
||||||
|
#if false
|
||||||
|
/// <summary>
|
||||||
|
/// Parses an application's command line parameters for easier consumption
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// At the time of the creation of this parser, there are already several command line
|
||||||
|
/// parsing libraries out there. Most of them, however, do way too much at once or at
|
||||||
|
/// the very least use one huge, untested clutter of classes and methods to arrive
|
||||||
|
/// at their results.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// This parser does nothing more than parse the command line arguments. It doesn't
|
||||||
|
/// interpret them and it doesn't check them for validity. Due to this, it can easily
|
||||||
|
/// be unit-tested and is an ideal building block to create actual command line
|
||||||
|
/// interpreters that connect the parameters to program instructions and or fill
|
||||||
|
/// structures in code.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public class CommandLine {
|
||||||
|
public static CommandLine Parse(string commandLine) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct CommandLineOption {
|
||||||
|
|
||||||
|
/// <summary>Contains the raw string the command line argument was parsed from</summary>
|
||||||
|
public string Raw;
|
||||||
|
/// <summary>Method used to specify the argument (either '-', '--' or '/')</summary>
|
||||||
|
public string Method;
|
||||||
|
/// <summary>Name of the command line argument</summary>
|
||||||
|
public string Name;
|
||||||
|
/// <summary>Value that has been assigned to the command line argument</summary>
|
||||||
|
public string Value;
|
||||||
|
/// <summary>Method used to assign the value (either '=', ':' or ' ')</summary>
|
||||||
|
public string Assignment;
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
|
@ -77,9 +77,9 @@ namespace Nuclex.Support.Plugins {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAddFilesWithOwnAssembly() {
|
public void TestAddFilesWithOwnAssembly() {
|
||||||
Assembly self = Assembly.GetAssembly(GetType());
|
|
||||||
|
|
||||||
PluginRepository testRepository = new PluginRepository();
|
PluginRepository testRepository = new PluginRepository();
|
||||||
|
|
||||||
|
Assembly self = Assembly.GetAssembly(GetType());
|
||||||
testRepository.AddFiles(self.Location);
|
testRepository.AddFiles(self.Location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,24 +35,24 @@ namespace Nuclex.Support.Scheduling {
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class OperationQueueTest {
|
public class OperationQueueTest {
|
||||||
|
|
||||||
#region interface IQueueOperationSubscriber
|
#region interface IOperationQueueSubscriber
|
||||||
|
|
||||||
/// <summary>Interface used to test the set waitable.</summary>
|
/// <summary>Interface used to test the operation queue</summary>
|
||||||
public interface IQueueOperationSubscriber {
|
public interface IOperationQueueSubscriber {
|
||||||
|
|
||||||
/// <summary>Called when the queue operations's progress changes</summary>
|
/// <summary>Called when the operations queue's progress changes</summary>
|
||||||
/// <param name="sender">Queue operation whose progress has changed</param>
|
/// <param name="sender">Operation queue whose progress has changed</param>
|
||||||
/// <param name="e">Contains the new progress achieved</param>
|
/// <param name="arguments">Contains the new progress achieved</param>
|
||||||
void ProgressChanged(object sender, ProgressReportEventArgs e);
|
void ProgressChanged(object sender, ProgressReportEventArgs arguments);
|
||||||
|
|
||||||
/// <summary>Called when the queue operation has ended</summary>
|
/// <summary>Called when the operation queue has ended</summary>
|
||||||
/// <param name="sender">Queue operation that as ended</param>
|
/// <param name="sender">Operation queue that as ended</param>
|
||||||
/// <param name="e">Not used</param>
|
/// <param name="arguments">Not used</param>
|
||||||
void Ended(object sender, EventArgs e);
|
void Ended(object sender, EventArgs arguments);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion // interface IQueueOperationSubscriber
|
#endregion // interface IOperationQueueSubscriber
|
||||||
|
|
||||||
#region class ProgressUpdateEventArgsMatcher
|
#region class ProgressUpdateEventArgsMatcher
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ namespace Nuclex.Support.Scheduling {
|
||||||
new TestOperation[] { operation1, operation2 }
|
new TestOperation[] { operation1, operation2 }
|
||||||
);
|
);
|
||||||
|
|
||||||
IQueueOperationSubscriber mockedSubscriber = mockSubscriber(testQueueOperation);
|
IOperationQueueSubscriber mockedSubscriber = mockSubscriber(testQueueOperation);
|
||||||
|
|
||||||
testQueueOperation.Start();
|
testQueueOperation.Start();
|
||||||
|
|
||||||
|
@ -212,9 +212,9 @@ namespace Nuclex.Support.Scheduling {
|
||||||
/// <summary>Mocks a subscriber for the events of an operation</summary>
|
/// <summary>Mocks a subscriber for the events of an operation</summary>
|
||||||
/// <param name="operation">Operation to mock an event subscriber for</param>
|
/// <param name="operation">Operation to mock an event subscriber for</param>
|
||||||
/// <returns>The mocked event subscriber</returns>
|
/// <returns>The mocked event subscriber</returns>
|
||||||
private IQueueOperationSubscriber mockSubscriber(Operation operation) {
|
private IOperationQueueSubscriber mockSubscriber(Operation operation) {
|
||||||
IQueueOperationSubscriber mockedSubscriber =
|
IOperationQueueSubscriber mockedSubscriber =
|
||||||
this.mockery.NewMock<IQueueOperationSubscriber>();
|
this.mockery.NewMock<IOperationQueueSubscriber>();
|
||||||
|
|
||||||
operation.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
|
operation.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
|
||||||
(operation as IProgressReporter).AsyncProgressChanged +=
|
(operation as IProgressReporter).AsyncProgressChanged +=
|
||||||
|
|
|
@ -42,23 +42,23 @@ namespace Nuclex.Support.Scheduling {
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public OperationQueue(IEnumerable<OperationType> childs) : this() {
|
public OperationQueue(IEnumerable<OperationType> childs) : this() {
|
||||||
|
|
||||||
// Construct a WeightedWaitable with the default weight for each
|
// Construct a WeightedTransaction with the default weight for each
|
||||||
// waitable and wrap it in an ObservedWaitable
|
// transaction and wrap it in an ObservedTransaction
|
||||||
foreach(OperationType operation in childs)
|
foreach(OperationType operation in childs)
|
||||||
this.children.Add(new WeightedWaitable<OperationType>(operation));
|
this.children.Add(new WeightedTransaction<OperationType>(operation));
|
||||||
|
|
||||||
// Since all waitables have a weight of 1.0, the total weight is
|
// Since all transactions have a weight of 1.0, the total weight is
|
||||||
// equal to the number of waitables in our list
|
// equal to the number of transactions 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 OperationQueue(IEnumerable<WeightedWaitable<OperationType>> childs) : this() {
|
public OperationQueue(IEnumerable<WeightedTransaction<OperationType>> childs) : this() {
|
||||||
|
|
||||||
// Construct an ObservedWaitablen around each of the WeightedWaitables
|
// Construct an ObservedTransactionn around each of the WeightedTransactions
|
||||||
foreach(WeightedWaitable<OperationType> operation in childs) {
|
foreach(WeightedTransaction<OperationType> operation in childs) {
|
||||||
this.children.Add(operation);
|
this.children.Add(operation);
|
||||||
|
|
||||||
// Sum up the total weight
|
// Sum up the total weight
|
||||||
|
@ -74,11 +74,11 @@ namespace Nuclex.Support.Scheduling {
|
||||||
asyncOperationProgressChanged
|
asyncOperationProgressChanged
|
||||||
);
|
);
|
||||||
|
|
||||||
this.children = new List<WeightedWaitable<OperationType>>();
|
this.children = new List<WeightedTransaction<OperationType>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Provides access to the child operations of this queue</summary>
|
/// <summary>Provides access to the child operations of this queue</summary>
|
||||||
public IList<WeightedWaitable<OperationType>> Children {
|
public IList<WeightedTransaction<OperationType>> Children {
|
||||||
get { return this.children; }
|
get { return this.children; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 waitable about the achieved progress.
|
/// Informs the observers of this transaction 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 waitable about the achieved progress.
|
/// Informs the observers of this transaction about the achieved progress.
|
||||||
/// Allows for classes derived from the Waitable class to easily provide
|
/// Allows for classes derived from the transaction 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
|
||||||
/// waitable's ProgressUpdateEventArgs class.
|
/// transaction'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;
|
||||||
|
@ -125,7 +125,7 @@ namespace Nuclex.Support.Scheduling {
|
||||||
/// and launches the operation by calling its Begin() method.
|
/// and launches the operation by calling its Begin() method.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void startCurrentOperation() {
|
private void startCurrentOperation() {
|
||||||
OperationType operation = this.children[this.currentOperationIndex].Waitable;
|
OperationType operation = this.children[this.currentOperationIndex].Transaction;
|
||||||
|
|
||||||
operation.AsyncEnded += this.asyncOperationEndedDelegate;
|
operation.AsyncEnded += this.asyncOperationEndedDelegate;
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ namespace Nuclex.Support.Scheduling {
|
||||||
/// counts up the accumulated progress of the queue.
|
/// counts up the accumulated progress of the queue.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void endCurrentOperation() {
|
private void endCurrentOperation() {
|
||||||
OperationType operation = this.children[this.currentOperationIndex].Waitable;
|
OperationType operation = this.children[this.currentOperationIndex].Transaction;
|
||||||
|
|
||||||
// Disconnect from the operation's events
|
// Disconnect from the operation's events
|
||||||
operation.AsyncEnded -= this.asyncOperationEndedDelegate;
|
operation.AsyncEnded -= this.asyncOperationEndedDelegate;
|
||||||
|
@ -217,7 +217,7 @@ namespace Nuclex.Support.Scheduling {
|
||||||
/// <summary>Delegate to the asyncOperationProgressUpdated() method</summary>
|
/// <summary>Delegate to the asyncOperationProgressUpdated() method</summary>
|
||||||
private EventHandler<ProgressReportEventArgs> asyncOperationProgressChangedDelegate;
|
private EventHandler<ProgressReportEventArgs> asyncOperationProgressChangedDelegate;
|
||||||
/// <summary>Operations being managed in the queue</summary>
|
/// <summary>Operations being managed in the queue</summary>
|
||||||
private List<WeightedWaitable<OperationType>> children;
|
private List<WeightedTransaction<OperationType>> children;
|
||||||
/// <summary>Summed weight of all operations in the queue</summary>
|
/// <summary>Summed weight of all operations in the queue</summary>
|
||||||
private float totalWeight;
|
private float totalWeight;
|
||||||
/// <summary>Accumulated weight of the operations already completed</summary>
|
/// <summary>Accumulated weight of the operations already completed</summary>
|
||||||
|
|
59
Source/Tracking/IdleStateEventArgs.Test.cs
Normal file
59
Source/Tracking/IdleStateEventArgs.Test.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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;
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.SyntaxHelpers;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the "idle state" event argument container</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class IdleStateEventArgsTest {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the idle state event arguments correctly report a non-idle state
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestIdleStateChangedToFalse() {
|
||||||
|
IdleStateEventArgs idleStateFalse = new IdleStateEventArgs(false);
|
||||||
|
|
||||||
|
Assert.IsFalse(idleStateFalse.Idle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the idle state event arguments correctly report an idle state
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestIdleStateChangedToTrue() {
|
||||||
|
IdleStateEventArgs idleStateFalse = new IdleStateEventArgs(true);
|
||||||
|
|
||||||
|
Assert.IsTrue(idleStateFalse.Idle);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
158
Source/Tracking/Internal/ObservedWeightedTransaction.Test.cs
Normal file
158
Source/Tracking/Internal/ObservedWeightedTransaction.Test.cs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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 NUnit.Framework.SyntaxHelpers;
|
||||||
|
using NMock2;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the observation wrapper of weighted transactions</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class ObservedWeightedTransactionTest {
|
||||||
|
|
||||||
|
#region interface IObservationSubscriber
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface used to test the observation wrapper of weighted transactions
|
||||||
|
/// </summary>
|
||||||
|
public interface IObservationSubscriber {
|
||||||
|
|
||||||
|
/// <summary>Will be invoked when an observed transaction's progress changes</summary>
|
||||||
|
void ProgressUpdated();
|
||||||
|
|
||||||
|
/// <summary>Will be invoked when an observed transaction completes</summary>
|
||||||
|
void Ended();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // interface IObservationSubscriber
|
||||||
|
|
||||||
|
#region class FunkyTransaction
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transaction that goes into the 'ended' state as soon as someone registers for
|
||||||
|
/// state change notifications
|
||||||
|
/// </summary>
|
||||||
|
private class FunkyTransaction : Transaction {
|
||||||
|
|
||||||
|
/// <summary>Manages registrations to the AsyncEnded event</summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Whether the transaction has already been ended</summary>
|
||||||
|
private int alreadyEnded;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class FunkyTransaction
|
||||||
|
|
||||||
|
/// <summary>Initialization routine executed before each test is run</summary>
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() {
|
||||||
|
this.mockery = new Mockery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Verifies that the constructor of the observation wrapper works</summary>
|
||||||
|
[Test]
|
||||||
|
public void TestConstructorWithAlreadyEndedTransaction() {
|
||||||
|
WeightedTransaction<Transaction> testTransaction = new WeightedTransaction<Transaction>(
|
||||||
|
Transaction.EndedDummy
|
||||||
|
);
|
||||||
|
|
||||||
|
IObservationSubscriber subscriber = this.mockery.NewMock<IObservationSubscriber>();
|
||||||
|
|
||||||
|
Expect.AtLeast(0).On(subscriber).Method("ProgressUpdated");
|
||||||
|
Expect.Once.On(subscriber).Method("Ended");
|
||||||
|
|
||||||
|
using(
|
||||||
|
ObservedWeightedTransaction<Transaction> test =
|
||||||
|
new ObservedWeightedTransaction<Transaction>(
|
||||||
|
testTransaction,
|
||||||
|
new ObservedWeightedTransaction<Transaction>.ReportDelegate(
|
||||||
|
subscriber.ProgressUpdated
|
||||||
|
),
|
||||||
|
new ObservedWeightedTransaction<Transaction>.ReportDelegate(
|
||||||
|
subscriber.Ended
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the constructor of the observation wrapper can handle a transaction
|
||||||
|
/// entering the 'ended' state right on subscription
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestConstructorWithEndingTransaction() {
|
||||||
|
WeightedTransaction<Transaction> testTransaction = new WeightedTransaction<Transaction>(
|
||||||
|
new FunkyTransaction()
|
||||||
|
);
|
||||||
|
|
||||||
|
IObservationSubscriber subscriber = this.mockery.NewMock<IObservationSubscriber>();
|
||||||
|
|
||||||
|
Expect.AtLeast(0).On(subscriber).Method("ProgressUpdated");
|
||||||
|
Expect.Once.On(subscriber).Method("Ended");
|
||||||
|
|
||||||
|
using(
|
||||||
|
ObservedWeightedTransaction<Transaction> test =
|
||||||
|
new ObservedWeightedTransaction<Transaction>(
|
||||||
|
testTransaction,
|
||||||
|
new ObservedWeightedTransaction<Transaction>.ReportDelegate(
|
||||||
|
subscriber.ProgressUpdated
|
||||||
|
),
|
||||||
|
new ObservedWeightedTransaction<Transaction>.ReportDelegate(
|
||||||
|
subscriber.Ended
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Mock object factory</summary>
|
||||||
|
private Mockery mockery;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
177
Source/Tracking/Internal/ObservedWeightedTransaction.cs
Normal file
177
Source/Tracking/Internal/ObservedWeightedTransaction.cs
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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 {
|
||||||
|
|
||||||
|
/// <summary>Transaction being observed by another object</summary>
|
||||||
|
/// <typeparam name="TransactionType">
|
||||||
|
/// Type of the transaction that is being observed
|
||||||
|
/// </typeparam>
|
||||||
|
internal class ObservedWeightedTransaction<TransactionType> : IDisposable
|
||||||
|
where TransactionType : Transaction {
|
||||||
|
|
||||||
|
/// <summary>Delegate for reporting progress updates</summary>
|
||||||
|
public delegate void ReportDelegate();
|
||||||
|
|
||||||
|
/// <summary>Initializes a new observed transaction</summary>
|
||||||
|
/// <param name="weightedTransaction">Weighted transaction being observed</param>
|
||||||
|
/// <param name="progressUpdateCallback">
|
||||||
|
/// Callback to invoke when the transaction's progress changes
|
||||||
|
/// </param>
|
||||||
|
/// <param name="endedCallback">
|
||||||
|
/// Callback to invoke when the transaction has ended
|
||||||
|
/// </param>
|
||||||
|
internal ObservedWeightedTransaction(
|
||||||
|
WeightedTransaction<TransactionType> 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();
|
||||||
|
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<ProgressReportEventArgs>(
|
||||||
|
asyncProgressChanged
|
||||||
|
);
|
||||||
|
this.progressReporter.AsyncProgressChanged += this.asyncProgressChangedEventHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Immediately releases all resources owned by the object</summary>
|
||||||
|
public void Dispose() {
|
||||||
|
asyncDisconnectEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Weighted transaction being observed</summary>
|
||||||
|
public WeightedTransaction<TransactionType> WeightedTransaction {
|
||||||
|
get { return this.weightedTransaction; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Amount of progress this transaction has achieved so far</summary>
|
||||||
|
public float Progress {
|
||||||
|
get { return this.progress; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Called when the observed transaction has ended</summary>
|
||||||
|
/// <param name="sender">Transaction that has ended</param>
|
||||||
|
/// <param name="e">Not used</param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Called when the progress of the observed transaction changes</summary>
|
||||||
|
/// <param name="sender">Transaction whose progress has changed</param>
|
||||||
|
/// <param name="e">Contains the updated progress</param>
|
||||||
|
private void asyncProgressChanged(object sender, ProgressReportEventArgs e) {
|
||||||
|
this.progress = e.Progress;
|
||||||
|
this.progressUpdateCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Unsubscribes from all events of the observed transaction</summary>
|
||||||
|
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<ProgressReportEventArgs> asyncProgressChangedEventHandler;
|
||||||
|
/// <summary>The observed transaction's progress reporting interface</summary>
|
||||||
|
private IProgressReporter progressReporter;
|
||||||
|
/// <summary>The weighted wable that is being observed</summary>
|
||||||
|
private WeightedTransaction<TransactionType> weightedTransaction;
|
||||||
|
/// <summary>Callback to invoke when the progress updates</summary>
|
||||||
|
private volatile ReportDelegate progressUpdateCallback;
|
||||||
|
/// <summary>Callback to invoke when the transaction ends</summary>
|
||||||
|
private volatile ReportDelegate endedCallback;
|
||||||
|
/// <summary>Progress achieved so far</summary>
|
||||||
|
private volatile float progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
|
@ -1,177 +0,0 @@
|
||||||
#region CPL License
|
|
||||||
/*
|
|
||||||
Nuclex Framework
|
|
||||||
Copyright (C) 2002-2008 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 {
|
|
||||||
|
|
||||||
/// <summary>Waitable being observed by another object</summary>
|
|
||||||
/// <typeparam name="WaitableType">
|
|
||||||
/// Type of the waitable that is being observed
|
|
||||||
/// </typeparam>
|
|
||||||
internal class ObservedWeightedWaitable<WaitableType> : IDisposable
|
|
||||||
where WaitableType : Waitable {
|
|
||||||
|
|
||||||
/// <summary>Delegate for reporting progress updates</summary>
|
|
||||||
public delegate void ReportDelegate();
|
|
||||||
|
|
||||||
/// <summary>Initializes a new observed waitable</summary>
|
|
||||||
/// <param name="weightedWaitable">Weighted waitable being observed</param>
|
|
||||||
/// <param name="progressUpdateCallback">
|
|
||||||
/// Callback to invoke when the waitable's progress changes
|
|
||||||
/// </param>
|
|
||||||
/// <param name="endedCallback">
|
|
||||||
/// Callback to invoke when the waitable has ended
|
|
||||||
/// </param>
|
|
||||||
internal ObservedWeightedWaitable(
|
|
||||||
WeightedWaitable<WaitableType> weightedWaitable,
|
|
||||||
ReportDelegate progressUpdateCallback,
|
|
||||||
ReportDelegate endedCallback
|
|
||||||
) {
|
|
||||||
this.weightedWaitable = weightedWaitable;
|
|
||||||
|
|
||||||
// See if this waitable has already ended (initial check for performance)
|
|
||||||
if(weightedWaitable.Waitable.Ended) {
|
|
||||||
|
|
||||||
this.progress = 1.0f;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
this.endedCallback = endedCallback;
|
|
||||||
this.progressUpdateCallback = progressUpdateCallback;
|
|
||||||
|
|
||||||
this.weightedWaitable.Waitable.AsyncEnded +=
|
|
||||||
new EventHandler(asyncEnded);
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// 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
|
|
||||||
// even though the waitable is in the 'Ended' state.
|
|
||||||
if(weightedWaitable.Waitable.Ended) {
|
|
||||||
this.progress = 1.0f;
|
|
||||||
} else {
|
|
||||||
this.progressReporter = this.weightedWaitable.Waitable as IProgressReporter;
|
|
||||||
|
|
||||||
if(this.progressReporter != null) {
|
|
||||||
this.asyncProgressChangedEventHandler = new EventHandler<ProgressReportEventArgs>(
|
|
||||||
asyncProgressChanged
|
|
||||||
);
|
|
||||||
|
|
||||||
this.progressReporter.AsyncProgressChanged +=
|
|
||||||
this.asyncProgressChangedEventHandler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Immediately releases all resources owned by the object</summary>
|
|
||||||
public void Dispose() {
|
|
||||||
asyncDisconnectEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Weighted waitable being observed</summary>
|
|
||||||
public WeightedWaitable<WaitableType> WeightedWaitable {
|
|
||||||
get { return this.weightedWaitable; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Amount of progress this waitable has achieved so far</summary>
|
|
||||||
public float Progress {
|
|
||||||
get { return this.progress; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Called when the observed waitable has ended</summary>
|
|
||||||
/// <param name="sender">Waitable that has ended</param>
|
|
||||||
/// <param name="e">Not used</param>
|
|
||||||
private void asyncEnded(object sender, EventArgs e) {
|
|
||||||
ReportDelegate endedCallback = this.endedCallback;
|
|
||||||
ReportDelegate progressUpdateCallback = 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 waitable doesn't report any progress at all, the set or queue
|
|
||||||
// owning us will have a percentage of waitables completed.
|
|
||||||
//
|
|
||||||
// There is the possibility of a race condition here, as a final progress
|
|
||||||
// report could have been generated by a thread running the waitable
|
|
||||||
// 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;
|
|
||||||
progressUpdateCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
endedCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Called when the progress of the observed waitable changes</summary>
|
|
||||||
/// <param name="sender">Waitable whose progress has changed</param>
|
|
||||||
/// <param name="e">Contains the updated progress</param>
|
|
||||||
private void asyncProgressChanged(object sender, ProgressReportEventArgs e) {
|
|
||||||
this.progress = e.Progress;
|
|
||||||
this.progressUpdateCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Unsubscribes from all events of the observed waitable</summary>
|
|
||||||
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.weightedWaitable.Waitable.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<ProgressReportEventArgs> asyncProgressChangedEventHandler;
|
|
||||||
/// <summary>The observed waitable's progress reporting interface</summary>
|
|
||||||
private IProgressReporter progressReporter;
|
|
||||||
/// <summary>The weighted wable that is being observed</summary>
|
|
||||||
private WeightedWaitable<WaitableType> weightedWaitable;
|
|
||||||
/// <summary>Callback to invoke when the progress updates</summary>
|
|
||||||
private volatile ReportDelegate progressUpdateCallback;
|
|
||||||
/// <summary>Callback to invoke when the waitable ends</summary>
|
|
||||||
private volatile ReportDelegate endedCallback;
|
|
||||||
/// <summary>Progress achieved so far</summary>
|
|
||||||
private volatile float progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Nuclex.Support.Tracking
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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 NUnit.Framework.SyntaxHelpers;
|
||||||
|
using NMock2;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the observation wrapper collection of weighted transactions</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class WeightedTransactionWrapperCollectionTest {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the wrapper collection is handing out the unwrapped transactions
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestWrapperCollection() {
|
||||||
|
WeightedTransaction<Transaction> transaction = new WeightedTransaction<Transaction>(
|
||||||
|
Transaction.EndedDummy
|
||||||
|
);
|
||||||
|
|
||||||
|
ObservedWeightedTransaction<Transaction> observed = new ObservedWeightedTransaction<Transaction>(
|
||||||
|
transaction,
|
||||||
|
endedCallback,
|
||||||
|
progressUpdatedCallback
|
||||||
|
);
|
||||||
|
|
||||||
|
WeightedTransactionWrapperCollection<Transaction> wrapper =
|
||||||
|
new WeightedTransactionWrapperCollection<Transaction>(
|
||||||
|
new ObservedWeightedTransaction<Transaction>[] { observed }
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.AreSame(transaction, wrapper[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dummy callback used as event subscriber in the tests</summary>
|
||||||
|
private void endedCallback() { }
|
||||||
|
|
||||||
|
/// <summary>Dummy callback used as event subscriber in the tests</summary>
|
||||||
|
private void progressUpdatedCallback() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
|
@ -27,36 +27,36 @@ using Nuclex.Support.Collections;
|
||||||
|
|
||||||
namespace Nuclex.Support.Tracking {
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
/// <summary>Collection of waitables with a weighting value</summary>
|
/// <summary>Collection of transactions with a weighting value</summary>
|
||||||
/// <typeparam name="WaitableType">Type of waitables to manage</typeparam>
|
/// <typeparam name="TransactionType">Type of transactions 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 WeightedWaitables. Internally, it merely wraps a collection of
|
/// stores WeightedTransactions. Internally, it merely wraps a collection of
|
||||||
/// an internal type used to keep track of the individual waitable's
|
/// an internal type used to keep track of the individual transaction's
|
||||||
/// progress in the WaitableSet and OperationQueue classes.
|
/// progress in the TransactionGroup and OperationQueue classes.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// It is read-only because the design requires a waitable to only ever
|
/// It is read-only because the design requires a transaction to only ever finish
|
||||||
/// finish once. If it was possible eg. to add items after a WaitableSet
|
/// once. If it was possible eg. to add items after a TransactionGroup had signalled
|
||||||
/// had signalled itself as being finished, it would be moved into an
|
/// itself as being finished, it would be moved into an unfinished state again.
|
||||||
/// unfinished state again. Also, an empty WaitableSet is, by definition,
|
/// Also, an empty TransactionGroup is, by definition, finished (simply because
|
||||||
/// finished (simply because there is no work to do) - unless the contents
|
/// there is no work to do) - unless the contents of the group are passed to the
|
||||||
/// of set are passed to the WaitableSet's constructor and never modified
|
/// TransactionGroup's constructor and never modified at all, the design would be
|
||||||
/// at all, the design would be violated as soon as ab instance of the
|
/// violated as soon as an instance of the TransactionGroup or OperationQueue
|
||||||
/// WaitableSet or OperationQueue classes was created.
|
/// classes was created.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal class WeightedWaitableWrapperCollection<WaitableType> :
|
internal class WeightedTransactionWrapperCollection<TransactionType> :
|
||||||
TransformingReadOnlyCollection<
|
TransformingReadOnlyCollection<
|
||||||
ObservedWeightedWaitable<WaitableType>, WeightedWaitable<WaitableType>
|
ObservedWeightedTransaction<TransactionType>, WeightedTransaction<TransactionType>
|
||||||
>
|
>
|
||||||
where WaitableType : Waitable {
|
where TransactionType : Transaction {
|
||||||
|
|
||||||
/// <summary>Initializes a new weighted waitable collection wrapper</summary>
|
/// <summary>Initializes a new weighted transaction collection wrapper</summary>
|
||||||
/// <param name="items">Items to be exposed as weighted waitables</param>
|
/// <param name="items">Items to be exposed as weighted transactions</param>
|
||||||
internal WeightedWaitableWrapperCollection(
|
internal WeightedTransactionWrapperCollection(
|
||||||
IList<ObservedWeightedWaitable<WaitableType>> items
|
IList<ObservedWeightedTransaction<TransactionType>> items
|
||||||
)
|
)
|
||||||
: base(items) { }
|
: base(items) { }
|
||||||
|
|
||||||
|
@ -69,10 +69,10 @@ 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<WaitableType> Transform(
|
protected override WeightedTransaction<TransactionType> Transform(
|
||||||
ObservedWeightedWaitable<WaitableType> item
|
ObservedWeightedTransaction<TransactionType> item
|
||||||
) {
|
) {
|
||||||
return item.WeightedWaitable;
|
return item.WeightedTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
59
Source/Tracking/ProgressReportEventArgs.Test.cs
Normal file
59
Source/Tracking/ProgressReportEventArgs.Test.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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;
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.SyntaxHelpers;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the progress report event argument container</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class ProgressReportEventArgsTest {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the progress report event arguments correctly report zero progress
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestZeroProgress() {
|
||||||
|
ProgressReportEventArgs zeroProgress = new ProgressReportEventArgs(0.0f);
|
||||||
|
|
||||||
|
Assert.AreEqual(0.0f, zeroProgress.Progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the progress report event arguments correctly report complete progress
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestCompleteProgress() {
|
||||||
|
ProgressReportEventArgs zeroProgress = new ProgressReportEventArgs(1.0f);
|
||||||
|
|
||||||
|
Assert.AreEqual(1.0f, zeroProgress.Progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
|
@ -91,21 +91,21 @@ namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
#endregion // class ProgressUpdateEventArgsMatcher
|
#endregion // class ProgressUpdateEventArgsMatcher
|
||||||
|
|
||||||
#region class TestWaitable
|
#region class TestTransaction
|
||||||
|
|
||||||
/// <summary>Waitable used for testing in this unit test</summary>
|
/// <summary>Transaction used for testing in this unit test</summary>
|
||||||
private class TestWaitable : Waitable, IProgressReporter {
|
private class TestTransaction : Transaction, 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 waitable's indicated progress</summary>
|
/// <summary>Changes the testing transaction's indicated progress</summary>
|
||||||
/// <param name="progress">New progress to be reported by the testing waitable</param>
|
/// <param name="progress">New progress to be reported by the testing transaction</param>
|
||||||
public void ChangeProgress(float progress) {
|
public void ChangeProgress(float progress) {
|
||||||
OnAsyncProgressChanged(progress);
|
OnAsyncProgressChanged(progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Transitions the waitable into the ended state</summary>
|
/// <summary>Transitions the transaction into the ended state</summary>
|
||||||
public void End() {
|
public void End() {
|
||||||
OnAsyncEnded();
|
OnAsyncEnded();
|
||||||
}
|
}
|
||||||
|
@ -113,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 waitable about the achieved progress.
|
/// Informs the observers of this transaction 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));
|
||||||
|
@ -122,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 waitable about the achieved progress.
|
/// Informs the observers of this transaction about the achieved progress.
|
||||||
/// Allows for classes derived from the Waitable class to easily provide
|
/// Allows for classes derived from the transaction 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
|
||||||
/// waitable's ProgressUpdateEventArgs class.
|
/// transaction'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;
|
||||||
|
@ -146,204 +146,238 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// <summary>Validates that the tracker properly sums the progress</summary>
|
/// <summary>Validates that the tracker properly sums the progress</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSummedProgress() {
|
public void TestSummedProgress() {
|
||||||
ProgressTracker tracker = new ProgressTracker();
|
using(ProgressTracker tracker = new ProgressTracker()) {
|
||||||
|
IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
||||||
|
|
||||||
IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
TestTransaction test1 = new TestTransaction();
|
||||||
|
TestTransaction test2 = new TestTransaction();
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
// Step 1
|
||||||
Method("IdleStateChanged").
|
{
|
||||||
WithAnyArguments();
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("IdleStateChanged").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
Expect.Exactly(2).On(mockedSubscriber).
|
// Since the progress is already at 0, these redundant reports are optional
|
||||||
Method("ProgressChanged").
|
Expect.Between(0, 2).On(mockedSubscriber).
|
||||||
With(
|
Method("ProgressChanged").
|
||||||
new Matcher[] {
|
With(
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
new Matcher[] {
|
||||||
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
||||||
}
|
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
|
||||||
TestWaitable test1 = new TestWaitable();
|
tracker.Track(test1);
|
||||||
tracker.Track(test1);
|
tracker.Track(test2);
|
||||||
TestWaitable test2 = new TestWaitable();
|
}
|
||||||
tracker.Track(test2);
|
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
// Step 2
|
||||||
Method("ProgressChanged").
|
{
|
||||||
With(
|
Expect.Once.On(mockedSubscriber).
|
||||||
new Matcher[] {
|
Method("ProgressChanged").
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
With(
|
||||||
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f))
|
new Matcher[] {
|
||||||
}
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
||||||
);
|
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
test1.ChangeProgress(0.5f);
|
test1.ChangeProgress(0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates that the tracker only removes waitables when the whole
|
/// Validates that the tracker only removes transactions 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 waitables right when they finished,
|
/// If the tracker would remove ended transactions 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();
|
using(ProgressTracker tracker = new ProgressTracker()) {
|
||||||
|
IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
||||||
|
|
||||||
IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
TestTransaction test1 = new TestTransaction();
|
||||||
|
TestTransaction test2 = new TestTransaction();
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
// Step 1
|
||||||
Method("IdleStateChanged").
|
{
|
||||||
WithAnyArguments();
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("IdleStateChanged").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
Expect.Exactly(2).On(mockedSubscriber).
|
// This is optional. The tracker's progress is currently 0, so there's no need
|
||||||
Method("ProgressChanged").
|
// to send out superfluous progress reports.
|
||||||
With(
|
Expect.Between(0, 2).On(mockedSubscriber).
|
||||||
new Matcher[] {
|
Method("ProgressChanged").
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
With(
|
||||||
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
|
new Matcher[] {
|
||||||
}
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
||||||
);
|
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
TestWaitable test1 = new TestWaitable();
|
tracker.Track(test1);
|
||||||
tracker.Track(test1);
|
tracker.Track(test2);
|
||||||
TestWaitable test2 = new TestWaitable();
|
}
|
||||||
tracker.Track(test2);
|
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
// Step 2
|
||||||
Method("ProgressChanged").
|
{
|
||||||
With(
|
Expect.Once.On(mockedSubscriber).
|
||||||
new Matcher[] {
|
Method("ProgressChanged").
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
With(
|
||||||
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f))
|
new Matcher[] {
|
||||||
}
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
||||||
);
|
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.25f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
test1.ChangeProgress(0.5f);
|
// Total progress should be 0.25 after this call (two transactions, one with
|
||||||
|
// 0% progress and one with 50% progress)
|
||||||
|
test1.ChangeProgress(0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
// Step 3
|
||||||
Method("ProgressChanged").
|
{
|
||||||
With(
|
Expect.Once.On(mockedSubscriber).
|
||||||
new Matcher[] {
|
Method("ProgressChanged").
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
With(
|
||||||
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.75f))
|
new Matcher[] {
|
||||||
}
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
||||||
);
|
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.75f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Total progress should be 0.75 after this call (one waitable at 1.0,
|
// Total progress should be 0.75 after this call (one transaction at 100%,
|
||||||
// the other one at 0.5). If the second waitable would be removed,
|
// the other one at 50%). If the second transaction would be removed by the tracker,
|
||||||
// the progress would jump to 0.5 instead.
|
// (which would be inappropriate!) the progress would falsely jump to 0.5 instead.
|
||||||
test2.End();
|
test2.End();
|
||||||
|
}
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
// Step 4
|
||||||
Method("ProgressChanged").
|
{
|
||||||
With(
|
Expect.Once.On(mockedSubscriber).
|
||||||
new Matcher[] {
|
Method("ProgressChanged").
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
With(
|
||||||
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f))
|
new Matcher[] {
|
||||||
}
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
||||||
);
|
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
Expect.Once.On(mockedSubscriber).
|
||||||
Method("IdleStateChanged").
|
Method("IdleStateChanged").
|
||||||
WithAnyArguments();
|
WithAnyArguments();
|
||||||
|
|
||||||
test1.End();
|
test1.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates that the tracker behaves correctly if it is fed with waitables
|
/// Validates that the tracker behaves correctly if it is fed with transactions
|
||||||
/// that have already ended.
|
/// that have already ended.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSoleEndedWaitable() {
|
public void TestSoleEndedTransaction() {
|
||||||
ProgressTracker tracker = new ProgressTracker();
|
using(ProgressTracker tracker = new ProgressTracker()) {
|
||||||
|
IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
||||||
|
|
||||||
IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
tracker.Track(Transaction.EndedDummy);
|
||||||
|
}
|
||||||
tracker.Track(Waitable.EndedDummy);
|
|
||||||
|
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates that the tracker behaves correctly if it is fed with waitables
|
/// Validates that the tracker behaves correctly if it is fed with transactions
|
||||||
/// that have already ended in addition to waitables that are actively executing.
|
/// that have already ended in addition to transactions that are actively executing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEndedWaitable() {
|
public void TestEndedTransaction() {
|
||||||
ProgressTracker tracker = new ProgressTracker();
|
using(ProgressTracker tracker = new ProgressTracker()) {
|
||||||
|
IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
||||||
|
|
||||||
IProgressTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
TestTransaction test1 = new TestTransaction();
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
// Step 1
|
||||||
Method("IdleStateChanged").
|
{
|
||||||
WithAnyArguments();
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("IdleStateChanged").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
Expect.Between(0, 1).On(mockedSubscriber).
|
||||||
Method("ProgressChanged").
|
Method("ProgressChanged").
|
||||||
With(
|
With(
|
||||||
new Matcher[] {
|
new Matcher[] {
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
||||||
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
|
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.0f))
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
TestWaitable test1 = new TestWaitable();
|
tracker.Track(test1);
|
||||||
tracker.Track(test1);
|
}
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
// Step 2
|
||||||
Method("ProgressChanged").
|
{
|
||||||
With(
|
Expect.Once.On(mockedSubscriber).
|
||||||
new Matcher[] {
|
Method("ProgressChanged").
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
With(
|
||||||
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.5f))
|
new Matcher[] {
|
||||||
}
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
||||||
);
|
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(0.5f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
tracker.Track(Waitable.EndedDummy);
|
tracker.Track(Transaction.EndedDummy);
|
||||||
|
}
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
// Step 3
|
||||||
Method("ProgressChanged").
|
{
|
||||||
With(
|
Expect.Once.On(mockedSubscriber).
|
||||||
new Matcher[] {
|
Method("ProgressChanged").
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
With(
|
||||||
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f))
|
new Matcher[] {
|
||||||
}
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)),
|
||||||
);
|
new ProgressReportEventArgsMatcher(new ProgressReportEventArgs(1.0f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
Expect.Once.On(mockedSubscriber).
|
||||||
Method("IdleStateChanged").
|
Method("IdleStateChanged").
|
||||||
WithAnyArguments();
|
WithAnyArguments();
|
||||||
|
|
||||||
test1.End();
|
test1.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to provoke a deadlock by re-entering the tracker from one of
|
/// Tries to provoke a deadlock by re-entering the tracker from one of its own events
|
||||||
/// its own events.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestProvokedDeadlock() {
|
public void TestProvokedDeadlock() {
|
||||||
ProgressTracker tracker = new ProgressTracker();
|
using(ProgressTracker tracker = new ProgressTracker()) {
|
||||||
|
TestTransaction test1 = new TestTransaction();
|
||||||
|
tracker.Track(test1);
|
||||||
|
|
||||||
TestWaitable test1 = new TestWaitable();
|
tracker.AsyncIdleStateChanged +=
|
||||||
tracker.Track(test1);
|
(EventHandler<IdleStateEventArgs>)delegate(object sender, IdleStateEventArgs arguments) {
|
||||||
|
tracker.Track(Transaction.EndedDummy);
|
||||||
|
};
|
||||||
|
|
||||||
tracker.AsyncIdleStateChanged +=
|
test1.End();
|
||||||
(EventHandler<IdleStateEventArgs>)delegate(object sender, IdleStateEventArgs arguments) {
|
}
|
||||||
tracker.Track(Waitable.EndedDummy);
|
|
||||||
};
|
|
||||||
|
|
||||||
test1.End();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Mocks a subscriber for the events of a tracker</summary>
|
/// <summary>Mocks a subscriber for the events of a tracker</summary>
|
||||||
|
|
|
@ -25,208 +25,195 @@ using System.Threading;
|
||||||
namespace Nuclex.Support.Tracking {
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helps tracking the progress of one or more waitable background processes
|
/// Helps tracking the progress of one or more background transactions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// This is useful if you want to display a progress bar for multiple
|
/// This is useful if you want to display a progress bar for multiple
|
||||||
/// Waitables but can not guarantee that no additional Waitables
|
/// transactions but can not guarantee that no additional transactions
|
||||||
/// will appear inmidst of execution.
|
/// will appear inmidst of execution.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// This class does not implement the <see cref="Waitable" /> interface itself
|
/// This class does not implement the <see cref="Transaction" /> interface itself
|
||||||
/// in order to not violate the design principles of Waitables which
|
/// in order to not violate the design principles of transactions which
|
||||||
/// guarantee that a <see cref="Waitable" /> will only finish once (whereas the
|
/// guarantee that a <see cref="Transaction" /> will only finish once (whereas the
|
||||||
/// progress tracker might 'finish' any number of times).
|
/// progress tracker might 'finish' any number of times).
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class ProgressTracker : IDisposable, IProgressReporter {
|
public class ProgressTracker : IDisposable, IProgressReporter {
|
||||||
|
|
||||||
#region class WaitableMatcher
|
#region class TransactionMatcher
|
||||||
|
|
||||||
/// <summary>Matches a direct Waitable to a fully wrapped one</summary>
|
/// <summary>Matches a direct transaction to a fully wrapped one</summary>
|
||||||
private class WaitableMatcher {
|
private class TransactionMatcher {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new Waitable matcher that matches against
|
/// Initializes a new transaction matcher that matches against
|
||||||
/// the specified Waitable
|
/// the specified transaction
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="toMatch">Waitable to match against</param>
|
/// <param name="toMatch">Transaction to match against</param>
|
||||||
public WaitableMatcher(Waitable toMatch) {
|
public TransactionMatcher(Transaction toMatch) {
|
||||||
this.toMatch = toMatch;
|
this.toMatch = toMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks whether the provided Waitable matches the comparison
|
/// Checks whether the provided transaction matches the comparison
|
||||||
/// Waitable of the instance
|
/// transaction of the instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="other">Waitable to match to the comparison Waitable</param>
|
/// <param name="other">Transaction to match to the comparison transaction</param>
|
||||||
public bool Matches(ObservedWeightedWaitable<Waitable> other) {
|
public bool Matches(ObservedWeightedTransaction<Transaction> other) {
|
||||||
return ReferenceEquals(other.WeightedWaitable.Waitable, this.toMatch);
|
return ReferenceEquals(other.WeightedTransaction.Transaction, this.toMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Waitable this instance compares against</summary>
|
/// <summary>Transaction this instance compares against</summary>
|
||||||
private Waitable toMatch;
|
private Transaction toMatch;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion // class WaitableMatcher
|
#endregion // class TransactionMatcher
|
||||||
|
|
||||||
/// <summary>Triggered when the idle state of the tracker changes</summary>
|
/// <summary>Triggered when the idle state of the tracker changes</summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The tracker is idle when no Waitables are being tracked in it. If you're
|
/// 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
|
/// 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,
|
/// showing or hiding the progress bar. The tracker starts off as idle because,
|
||||||
/// upon construction, its list of Waitables will be empty.
|
/// upon construction, its list of transactions will be empty.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public event EventHandler<IdleStateEventArgs> AsyncIdleStateChanged;
|
public event EventHandler<IdleStateEventArgs> AsyncIdleStateChanged;
|
||||||
|
|
||||||
/// <summary>Triggered when the total progress has changed</summary>
|
/// <summary>Triggered when the total progress has changed</summary>
|
||||||
public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged;
|
public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged;
|
||||||
|
|
||||||
/// <summary>Initializes a new Waitable tracker</summary>
|
/// <summary>Initializes a new transaction tracker</summary>
|
||||||
public ProgressTracker() {
|
public ProgressTracker() {
|
||||||
|
|
||||||
this.trackedWaitables = new List<ObservedWeightedWaitable<Waitable>>();
|
this.trackedTransactions = new List<ObservedWeightedTransaction<Transaction>>();
|
||||||
this.idle = true;
|
this.idle = true;
|
||||||
|
|
||||||
this.asyncEndedDelegate =
|
this.asyncEndedDelegate =
|
||||||
new ObservedWeightedWaitable<Waitable>.ReportDelegate(asyncEnded);
|
new ObservedWeightedTransaction<Transaction>.ReportDelegate(asyncEnded);
|
||||||
this.asyncProgressUpdatedDelegate =
|
this.asyncProgressUpdatedDelegate =
|
||||||
new ObservedWeightedWaitable<Waitable>.ReportDelegate(asyncProgressChanged);
|
new ObservedWeightedTransaction<Transaction>.ReportDelegate(asyncProgressChanged);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
lock(this.trackedWaitables) {
|
lock(this.trackedTransactions) {
|
||||||
|
|
||||||
// Get rid of all Waitables we're tracking. This unsubscribes the
|
// Get rid of all transactions we're tracking. This unsubscribes the
|
||||||
// observers from the events of the Waitables and stops us from
|
// observers from the events of the transactions and stops us from
|
||||||
// being kept alive and receiving any further events if some of the
|
// being kept alive and receiving any further events if some of the
|
||||||
// tracked Waitables are still executing.
|
// tracked transactions are still executing.
|
||||||
for(int index = 0; index < this.trackedWaitables.Count; ++index)
|
for(int index = 0; index < this.trackedTransactions.Count; ++index)
|
||||||
this.trackedWaitables[index].Dispose();
|
this.trackedTransactions[index].Dispose();
|
||||||
|
|
||||||
// Help the GC a bit by untangling the references :)
|
// Help the GC a bit by untangling the references :)
|
||||||
this.trackedWaitables.Clear();
|
this.trackedTransactions.Clear();
|
||||||
this.trackedWaitables = null;
|
this.trackedTransactions = null;
|
||||||
|
|
||||||
} // lock
|
} // lock
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Begins tracking the specified waitable background process</summary>
|
/// <summary>Begins tracking the specified background transactions</summary>
|
||||||
/// <param name="waitable">Waitable background process to be tracked</param>
|
/// <param name="transaction">Background transaction to be tracked</param>
|
||||||
public void Track(Waitable waitable) {
|
public void Track(Transaction transaction) {
|
||||||
Track(waitable, 1.0f);
|
Track(transaction, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Begins tracking the specified waitable background process</summary>
|
/// <summary>Begins tracking the specified background transaction</summary>
|
||||||
/// <param name="waitable">Waitable background process to be tracked</param>
|
/// <param name="transaction">Background transaction to be tracked</param>
|
||||||
/// <param name="weight">Weight to assign to this waitable background process</param>
|
/// <param name="weight">Weight to assign to this background transaction</param>
|
||||||
public void Track(Waitable waitable, float weight) {
|
public void Track(Transaction transaction, float weight) {
|
||||||
|
|
||||||
// Add the new Waitable into the tracking list. This has to be done
|
// 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
|
// inside a lock to prevent issues with the progressUpdate callback, which could
|
||||||
// access the totalWeight field before it has been updated to reflect the
|
// access the totalWeight field before it has been updated to reflect the
|
||||||
// new Waitable added to the collection.
|
// new transaction added to the collection.
|
||||||
lock(this.trackedWaitables) {
|
lock(this.trackedTransactions) {
|
||||||
|
|
||||||
bool wasEmpty = (this.trackedWaitables.Count == 0);
|
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<Transaction>(
|
||||||
|
new WeightedTransaction<Transaction>(transaction, weight),
|
||||||
|
this.asyncProgressUpdatedDelegate,
|
||||||
|
this.asyncEndedDelegate
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // Not ended -- Transation is still running
|
||||||
|
|
||||||
|
// Construct a new transation observer and add the transaction to our
|
||||||
|
// list of tracked transactions.
|
||||||
|
ObservedWeightedTransaction<Transaction> observedTransaction =
|
||||||
|
new ObservedWeightedTransaction<Transaction>(
|
||||||
|
new WeightedTransaction<Transaction>(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
|
// 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
|
// any incoming progress updates will be stopped from the danger of a
|
||||||
// division-by-zero from the potentially still zeroed totalWeight by the lock.
|
// division-by-zero from the potentially still zeroed totalWeight by the lock.
|
||||||
this.totalWeight += weight;
|
this.totalWeight += weight;
|
||||||
|
|
||||||
if(waitable.Ended) {
|
// All done, the total progress is different now, so force a recalculation and
|
||||||
|
// send out the AsyncProgressUpdated event.
|
||||||
// If the ended Waitable would become the only Waitable in the list,
|
recalculateProgress();
|
||||||
// there's no sense in doing anything at all because it would have to be
|
|
||||||
// thrown right out again. Only add the Waitable when there are other
|
|
||||||
// running Waitables 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 Waitable has been added to the tracked Waitables list.
|
|
||||||
this.trackedWaitables.Add(
|
|
||||||
new ObservedWeightedWaitable<Waitable>(
|
|
||||||
new WeightedWaitable<Waitable>(waitable, weight),
|
|
||||||
this.asyncProgressUpdatedDelegate,
|
|
||||||
this.asyncEndedDelegate
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// All done, the total progress is different now, so force a recalculation and
|
|
||||||
// send out the AsyncProgressUpdated event.
|
|
||||||
recalculateProgress();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else { // Not ended -- Waitable is still running
|
|
||||||
|
|
||||||
// Construct a new Waitable observer and add the Waitable to our
|
|
||||||
// list of tracked Waitables.
|
|
||||||
ObservedWeightedWaitable<Waitable> observedWaitable =
|
|
||||||
new ObservedWeightedWaitable<Waitable>(
|
|
||||||
new WeightedWaitable<Waitable>(waitable, weight),
|
|
||||||
this.asyncProgressUpdatedDelegate,
|
|
||||||
this.asyncEndedDelegate
|
|
||||||
);
|
|
||||||
|
|
||||||
this.trackedWaitables.Add(observedWaitable);
|
|
||||||
|
|
||||||
// If this is the first Waitable to be added to the list, tell our
|
|
||||||
// owner that we're idle no longer!
|
|
||||||
if(wasEmpty)
|
|
||||||
setIdle(false);
|
|
||||||
|
|
||||||
// All done, the total progress is different now, so force a recalculation and
|
|
||||||
// send out the AsyncProgressUpdated event.
|
|
||||||
recalculateProgress();
|
|
||||||
|
|
||||||
// The Waitable might have ended before we had registered to its AsyncEnded
|
|
||||||
// event, so we have to do this to be on the safe side. This might cause
|
|
||||||
// asyncEnded() to be called twice, but that's not a problem in this
|
|
||||||
// implementation and improves performance and simplicity for the normal path.
|
|
||||||
if(waitable.Ended) {
|
|
||||||
asyncEnded();
|
|
||||||
observedWaitable.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // if Waitable ended
|
|
||||||
|
|
||||||
} // lock
|
} // lock
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Stops tracking the specified waitable background process</summary>
|
/// <summary>Stops tracking the specified background transaction</summary>
|
||||||
/// <param name="waitable">Waitable background process to stop tracking of</param>
|
/// <param name="transaction">Background transaction to stop tracking of</param>
|
||||||
public void Untrack(Waitable waitable) {
|
public void Untrack(Transaction transaction) {
|
||||||
lock(this.trackedWaitables) {
|
lock(this.trackedTransactions) {
|
||||||
|
|
||||||
// Locate the object to be untracked in our collection
|
// Locate the object to be untracked in our collection
|
||||||
int removeIndex = this.trackedWaitables.FindIndex(
|
int removeIndex = this.trackedTransactions.FindIndex(
|
||||||
new Predicate<ObservedWeightedWaitable<Waitable>>(
|
new Predicate<ObservedWeightedTransaction<Transaction>>(
|
||||||
new WaitableMatcher(waitable).Matches
|
new TransactionMatcher(transaction).Matches
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if(removeIndex == -1)
|
if(removeIndex == -1)
|
||||||
throw new InvalidOperationException("Item is not being tracked");
|
throw new InvalidOperationException("Item is not being tracked");
|
||||||
|
|
||||||
// Remove and dispose the Waitable the user wants to untrack
|
// Remove and dispose the transaction the user wants to untrack
|
||||||
{
|
{
|
||||||
ObservedWeightedWaitable<Waitable> wrappedWaitable =
|
ObservedWeightedTransaction<Transaction> wrappedTransaction =
|
||||||
this.trackedWaitables[removeIndex];
|
this.trackedTransactions[removeIndex];
|
||||||
|
|
||||||
this.trackedWaitables.RemoveAt(removeIndex);
|
this.trackedTransactions.RemoveAt(removeIndex);
|
||||||
wrappedWaitable.Dispose();
|
wrappedTransaction.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the list is empty, then we're back in the idle state
|
// If the list is empty, then we're back in the idle state
|
||||||
if(this.trackedWaitables.Count == 0) {
|
if(this.trackedTransactions.Count == 0) {
|
||||||
|
|
||||||
this.totalWeight = 0.0f;
|
this.totalWeight = 0.0f;
|
||||||
|
|
||||||
|
@ -235,12 +222,12 @@ namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Rebuild the total weight from scratch. Subtracting the removed Waitable's
|
// Rebuild the total weight from scratch. Subtracting the removed transaction's
|
||||||
// weight would work, too, but we might accumulate rounding errors making the sum
|
// weight would work, too, but we might accumulate rounding errors making the sum
|
||||||
// drift slowly away from the actual value.
|
// drift slowly away from the actual value.
|
||||||
this.totalWeight = 0.0f;
|
this.totalWeight = 0.0f;
|
||||||
for(int index = 0; index < this.trackedWaitables.Count; ++index)
|
for(int index = 0; index < this.trackedTransactions.Count; ++index)
|
||||||
this.totalWeight += this.trackedWaitables[index].WeightedWaitable.Weight;
|
this.totalWeight += this.trackedTransactions[index].WeightedTransaction.Weight;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +239,7 @@ namespace Nuclex.Support.Tracking {
|
||||||
get { return this.idle; }
|
get { return this.idle; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Current summed progress of the tracked Waitables</summary>
|
/// <summary>Current summed progress of the tracked transactions</summary>
|
||||||
public float Progress {
|
public float Progress {
|
||||||
get { return this.progress; }
|
get { return this.progress; }
|
||||||
}
|
}
|
||||||
|
@ -278,59 +265,61 @@ namespace Nuclex.Support.Tracking {
|
||||||
float totalProgress = 0.0f;
|
float totalProgress = 0.0f;
|
||||||
|
|
||||||
// Lock the collection to avoid trouble when someone tries to remove one
|
// Lock the collection to avoid trouble when someone tries to remove one
|
||||||
// of our tracked Waitables while we're just doing a progress update
|
// of our tracked transactions while we're just doing a progress update
|
||||||
lock(this.trackedWaitables) {
|
lock(this.trackedTransactions) {
|
||||||
|
|
||||||
// This is a safety measure. In theory, even after all Waitables have
|
// This is a safety measure. In theory, even after all transactions have
|
||||||
// ended and the collection of tracked Waitables is cleared, a waiting
|
// ended and the collection of tracked transactions is cleared, a waiting
|
||||||
// thread might deliver another progress update causing this method to
|
// thread might deliver another progress update causing this method to
|
||||||
// be entered. In this case, the right thing is to do nothing at all.
|
// be entered. In this case, the right thing is to do nothing at all.
|
||||||
if(this.totalWeight == 0.0f)
|
if(this.totalWeight == 0.0f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Sum up the total progress
|
// Sum up the total progress
|
||||||
for(int index = 0; index < this.trackedWaitables.Count; ++index) {
|
for(int index = 0; index < this.trackedTransactions.Count; ++index) {
|
||||||
float weight = this.trackedWaitables[index].WeightedWaitable.Weight;
|
float weight = this.trackedTransactions[index].WeightedTransaction.Weight;
|
||||||
totalProgress += this.trackedWaitables[index].Progress * weight;
|
totalProgress += this.trackedTransactions[index].Progress * weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This also needs to be in the lock to guarantee that the totalWeight is
|
// This also needs to be in the lock to guarantee that the totalWeight is
|
||||||
// the one for the number of Waitables we just summed -- by design,
|
// the one for the number of transactions we just summed -- by design,
|
||||||
// the total weight always has to be updated at the same time as the collection.
|
// the total weight always has to be updated at the same time as the collection.
|
||||||
totalProgress /= this.totalWeight;
|
totalProgress /= this.totalWeight;
|
||||||
|
|
||||||
// Finally, trigger the event
|
// Finally, trigger the event if the progress has changed
|
||||||
this.progress = totalProgress;
|
if(totalProgress != this.progress) {
|
||||||
OnAsyncProgressUpdated(totalProgress);
|
this.progress = totalProgress;
|
||||||
|
OnAsyncProgressUpdated(totalProgress);
|
||||||
|
}
|
||||||
|
|
||||||
} // lock
|
} // lock
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Called when one of the tracked Waitables has ended</summary>
|
/// <summary>Called when one of the tracked transactions has ended</summary>
|
||||||
private void asyncEnded() {
|
private void asyncEnded() {
|
||||||
lock(this.trackedWaitables) {
|
lock(this.trackedTransactions) {
|
||||||
|
|
||||||
// If any Waitables in the list are still going, keep the entire list.
|
// 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
|
// This behavior is intentional in order to prevent the tracker's progress from
|
||||||
// jumping back repeatedly when multiple tracked Waitables come to an end.
|
// jumping back repeatedly when multiple tracked transactions come to an end.
|
||||||
for(int index = 0; index < this.trackedWaitables.Count; ++index)
|
for(int index = 0; index < this.trackedTransactions.Count; ++index)
|
||||||
if(!this.trackedWaitables[index].WeightedWaitable.Waitable.Ended)
|
if(!this.trackedTransactions[index].WeightedTransaction.Transaction.Ended)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// All Waitables have finished, get rid of the wrappers and make a
|
// All transactions have finished, get rid of the wrappers and make a
|
||||||
// fresh start for future Waitables to be tracked. No need to call
|
// fresh start for future transactions to be tracked. No need to call
|
||||||
// Dispose() since, as a matter of fact, when the Waitable
|
// Dispose() since, as a matter of fact, when the transaction
|
||||||
this.trackedWaitables.Clear();
|
this.trackedTransactions.Clear();
|
||||||
this.totalWeight = 0.0f;
|
this.totalWeight = 0.0f;
|
||||||
|
|
||||||
// Notify our owner that we're idle now. This line is only reached when all
|
// Notify our owner that we're idle now. This line is only reached when all
|
||||||
// Waitables were finished, so it's safe to trigger this here.
|
// transactions were finished, so it's safe to trigger this here.
|
||||||
setIdle(true);
|
setIdle(true);
|
||||||
|
|
||||||
} // lock
|
} // lock
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Called when one of the tracked Waitables has achieved progress</summary>
|
/// <summary>Called when one of the tracked transactions has achieved progress</summary>
|
||||||
private void asyncProgressChanged() {
|
private void asyncProgressChanged() {
|
||||||
recalculateProgress();
|
recalculateProgress();
|
||||||
}
|
}
|
||||||
|
@ -338,7 +327,7 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// <summary>Changes the idle state</summary>
|
/// <summary>Changes the idle state</summary>
|
||||||
/// <param name="idle">Whether or not the tracker is currently idle</param>
|
/// <param name="idle">Whether or not the tracker is currently idle</param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This method expects to be called during a lock() on trackedWaitables!
|
/// This method expects to be called during a lock() on trackedTransactions!
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void setIdle(bool idle) {
|
private void setIdle(bool idle) {
|
||||||
this.idle = idle;
|
this.idle = idle;
|
||||||
|
@ -348,16 +337,16 @@ namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
/// <summary>Whether the tracker is currently idle</summary>
|
/// <summary>Whether the tracker is currently idle</summary>
|
||||||
private volatile bool idle;
|
private volatile bool idle;
|
||||||
/// <summary>Current summed progress of the tracked Waitables</summary>
|
/// <summary>Current summed progress of the tracked transactions</summary>
|
||||||
private volatile float progress;
|
private volatile float progress;
|
||||||
/// <summary>Total weight of all Waitables being tracked</summary>
|
/// <summary>Total weight of all transactions being tracked</summary>
|
||||||
private volatile float totalWeight;
|
private volatile float totalWeight;
|
||||||
/// <summary>Waitables being tracked by this tracker</summary>
|
/// <summary>Transactions being tracked by this tracker</summary>
|
||||||
private List<ObservedWeightedWaitable<Waitable>> trackedWaitables;
|
private List<ObservedWeightedTransaction<Transaction>> trackedTransactions;
|
||||||
/// <summary>Delegate for the asyncEnded() method</summary>
|
/// <summary>Delegate for the asyncEnded() method</summary>
|
||||||
private ObservedWeightedWaitable<Waitable>.ReportDelegate asyncEndedDelegate;
|
private ObservedWeightedTransaction<Transaction>.ReportDelegate asyncEndedDelegate;
|
||||||
/// <summary>Delegate for the asyncProgressUpdated() method</summary>
|
/// <summary>Delegate for the asyncProgressUpdated() method</summary>
|
||||||
private ObservedWeightedWaitable<Waitable>.ReportDelegate asyncProgressUpdatedDelegate;
|
private ObservedWeightedTransaction<Transaction>.ReportDelegate asyncProgressUpdatedDelegate;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,11 +33,11 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// store a new exception) and re-throw them when in ReraiseExceptions()
|
/// store a new exception) and re-throw them when in ReraiseExceptions()
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Like in the Waitable class, the contract requires you to always call
|
/// Like in the transaction class, the contract requires you to always call
|
||||||
/// OnAsyncEnded(), no matter what the outcome of your 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 : Transaction {
|
||||||
|
|
||||||
#region class EndedDummyRequest
|
#region class EndedDummyRequest
|
||||||
|
|
||||||
|
|
60
Source/Tracking/StatusReportEventArgs.Test.cs
Normal file
60
Source/Tracking/StatusReportEventArgs.Test.cs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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;
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.SyntaxHelpers;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the status report event argument container</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class StatusReportEventArgsTest {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the status report event arguments correctly reports an empty status
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyStatus() {
|
||||||
|
StatusReportEventArgs emptyStatus = new StatusReportEventArgs(string.Empty);
|
||||||
|
|
||||||
|
Assert.AreEqual(string.Empty, emptyStatus.Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the status report event arguments correctly reports simple
|
||||||
|
/// status indications
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestSimpleStatus() {
|
||||||
|
StatusReportEventArgs emptyStatus = new StatusReportEventArgs("hello world");
|
||||||
|
|
||||||
|
Assert.AreEqual("hello world", emptyStatus.Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
189
Source/Tracking/Transaction.Test.cs
Normal file
189
Source/Tracking/Transaction.Test.cs
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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 NMock2;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the transaction class</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class TransactionTest {
|
||||||
|
|
||||||
|
#region interface ITransactionSubscriber
|
||||||
|
|
||||||
|
/// <summary>Interface used to test the transaction</summary>
|
||||||
|
public interface ITransactionSubscriber {
|
||||||
|
|
||||||
|
/// <summary>Called when the set transaction has ended</summary>
|
||||||
|
/// <param name="sender">Transaction group that as ended</param>
|
||||||
|
/// <param name="arguments">Not used</param>
|
||||||
|
void Ended(object sender, EventArgs arguments);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // interface ITransactionGroupSubscriber
|
||||||
|
|
||||||
|
#region class TestTransaction
|
||||||
|
|
||||||
|
/// <summary>Transaction used for testing in this unit test</summary>
|
||||||
|
private class TestTransaction : Transaction {
|
||||||
|
|
||||||
|
/// <summary>Transitions the transaction into the ended state</summary>
|
||||||
|
public void End() {
|
||||||
|
OnAsyncEnded();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class TestWiatable
|
||||||
|
|
||||||
|
/// <summary>Initialization routine executed before each test is run</summary>
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() {
|
||||||
|
this.mockery = new Mockery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the transaction throws an exception when it is ended multiple times
|
||||||
|
/// </summary>
|
||||||
|
[Test, ExpectedException(typeof(InvalidOperationException))]
|
||||||
|
public void TestThrowOnRepeatedlyEndedTransaction() {
|
||||||
|
TestTransaction test = new TestTransaction();
|
||||||
|
test.End();
|
||||||
|
test.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the Ended event of the transaction is correctly delivered if
|
||||||
|
/// the transaction ends after the subscription already took place
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestEndedEventAfterSubscription() {
|
||||||
|
TestTransaction test = new TestTransaction();
|
||||||
|
|
||||||
|
ITransactionSubscriber mockedSubscriber = mockSubscriber(test);
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("Ended").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
|
test.End();
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the Ended event of the transaction is correctly delivered if
|
||||||
|
/// the transaction is already done when the subscription takes place
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestEndedEventDuingSubscription() {
|
||||||
|
TestTransaction test = new TestTransaction();
|
||||||
|
test.End();
|
||||||
|
|
||||||
|
ITransactionSubscriber mockedSubscriber =
|
||||||
|
this.mockery.NewMock<ITransactionSubscriber>();
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("Ended").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
|
test.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the Wait() method of the transaction works as expected
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestWaitUnlimited() {
|
||||||
|
TestTransaction test = new TestTransaction();
|
||||||
|
|
||||||
|
// We can only do a positive test here without slowing down the unit test
|
||||||
|
test.End();
|
||||||
|
test.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the Wait() method of the transaction works as expected using
|
||||||
|
/// a millisecond count as its argument
|
||||||
|
/// </summary>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the Wait() method of the transaction works as expected using
|
||||||
|
/// a TimeSpan as its argument
|
||||||
|
/// </summary>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Mocks a subscriber for the events of a transaction</summary>
|
||||||
|
/// <param name="transaction">Transaction to mock an event subscriber for</param>
|
||||||
|
/// <returns>The mocked event subscriber</returns>
|
||||||
|
private ITransactionSubscriber mockSubscriber(Transaction transaction) {
|
||||||
|
ITransactionSubscriber mockedSubscriber =
|
||||||
|
this.mockery.NewMock<ITransactionSubscriber>();
|
||||||
|
|
||||||
|
transaction.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
|
||||||
|
|
||||||
|
return mockedSubscriber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Mock object factory</summary>
|
||||||
|
private Mockery mockery;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
|
@ -28,46 +28,46 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// By encapsulating long-running operations which will ideally be running in
|
/// By encapsulating long-running operations which will ideally be running in
|
||||||
/// a background thread in a class that's derived from <see cref="Waitable" />
|
/// a background thread in a class that's derived from <see cref="Transaction" />
|
||||||
/// you can wait for the completion of the operation and optionally even receive
|
/// 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
|
/// 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
|
/// bar, loading screen or some other means of entertaining the user while he
|
||||||
/// waits for the task to complete.
|
/// waits for the task to complete.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// You can register callbacks which will be fired once the <see cref="Waitable" />
|
/// You can register callbacks which will be fired once the <see cref="Transaction" />
|
||||||
/// task has completed. This class deliberately does not provide an Execute()
|
/// task has completed. This class deliberately does not provide an Execute()
|
||||||
/// method or anything similar to clearly seperate the initiation of an operation
|
/// method or anything similar to clearly seperate the initiation of an operation
|
||||||
/// from just monitoring it. By omitting an Execute() method, it also becomes
|
/// from just monitoring it. By omitting an Execute() method, it also becomes
|
||||||
/// possible to construct a Waitable just-in-time when it is explicitely being
|
/// possible to construct a transaction just-in-time when it is explicitely being
|
||||||
/// asked for.
|
/// asked for.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public abstract class Waitable {
|
public abstract class Transaction {
|
||||||
|
|
||||||
#region class EndedDummyWaitable
|
#region class EndedDummyTransaction
|
||||||
|
|
||||||
/// <summary>Dummy waitable which always is in the 'ended' state</summary>
|
/// <summary>Dummy transaction which always is in the 'ended' state</summary>
|
||||||
private class EndedDummyWaitable : Waitable {
|
private class EndedDummyTransaction : Transaction {
|
||||||
|
|
||||||
/// <summary>Initializes a new ended dummy waitable</summary>
|
/// <summary>Initializes a new ended dummy transaction</summary>
|
||||||
public EndedDummyWaitable() {
|
public EndedDummyTransaction() {
|
||||||
OnAsyncEnded();
|
OnAsyncEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion // class EndedDummyWaitable
|
#endregion // class EndedDummyTransaction
|
||||||
|
|
||||||
/// <summary>A dummy waitable that's always in the 'ended' state</summary>
|
/// <summary>A dummy transaction 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 waitable that's lazily created is accessed after the original
|
/// when a transaction 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 Transaction EndedDummy = new EndedDummyTransaction();
|
||||||
|
|
||||||
/// <summary>Will be triggered when the Waitable has ended</summary>
|
/// <summary>Will be triggered when the transaction has ended</summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If the process is already finished when a client registers to this event,
|
/// If the process is already finished when a client registers to this event,
|
||||||
/// the registered callback will be invoked synchronously right when the
|
/// the registered callback will be invoked synchronously right when the
|
||||||
|
@ -149,12 +149,12 @@ namespace Nuclex.Support.Tracking {
|
||||||
return WaitHandle.WaitOne(timeoutMilliseconds, false);
|
return WaitHandle.WaitOne(timeoutMilliseconds, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Whether the Waitable has ended already</summary>
|
/// <summary>Whether the transaction has ended already</summary>
|
||||||
public virtual bool Ended {
|
public virtual bool Ended {
|
||||||
get { return this.ended; }
|
get { return this.ended; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>WaitHandle that can be used to wait for the Waitable to end</summary>
|
/// <summary>WaitHandle that can be used to wait for the transaction to end</summary>
|
||||||
public virtual WaitHandle WaitHandle {
|
public virtual WaitHandle WaitHandle {
|
||||||
get {
|
get {
|
||||||
|
|
||||||
|
@ -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 Waitable that's
|
/// the OnAsyncEnded() method is called on any instance of transaction 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 waitable is not ended more than once. By guaranteeing that
|
// Make sure the transaction is not ended more than once. By guaranteeing that
|
||||||
// a waitable can only be ended once, we allow users of this class to
|
// a transaction 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 Waitable has already been ended");
|
throw new InvalidOperationException("The transaction 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 waitable is completed</summary>
|
/// <summary>Event that will be set when the transaction 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.
|
263
Source/Tracking/TransactionGroup.Test.cs
Normal file
263
Source/Tracking/TransactionGroup.Test.cs
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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;
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NMock2;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the transaction group class</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class TransactionGroupTest {
|
||||||
|
|
||||||
|
#region interface ITransactionGroupSubscriber
|
||||||
|
|
||||||
|
/// <summary>Interface used to test the transaction group</summary>
|
||||||
|
public interface ITransactionGroupSubscriber {
|
||||||
|
|
||||||
|
/// <summary>Called when the transaction group's progress changes</summary>
|
||||||
|
/// <param name="sender">Transaction group whose progress has changed</param>
|
||||||
|
/// <param name="arguments">Contains the new progress achieved</param>
|
||||||
|
void ProgressChanged(object sender, ProgressReportEventArgs arguments);
|
||||||
|
|
||||||
|
/// <summary>Called when the transaction group has ended</summary>
|
||||||
|
/// <param name="sender">Transaction group that as ended</param>
|
||||||
|
/// <param name="arguments">Not used</param>
|
||||||
|
void Ended(object sender, EventArgs arguments);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // interface ITransactionGroupSubscriber
|
||||||
|
|
||||||
|
#region class ProgressUpdateEventArgsMatcher
|
||||||
|
|
||||||
|
/// <summary>Compares two ProgressUpdateEventArgsInstances for NMock validation</summary>
|
||||||
|
private class ProgressUpdateEventArgsMatcher : Matcher {
|
||||||
|
|
||||||
|
/// <summary>Initializes a new ProgressUpdateEventArgsMatcher </summary>
|
||||||
|
/// <param name="expected">Expected progress update event arguments</param>
|
||||||
|
public ProgressUpdateEventArgsMatcher(ProgressReportEventArgs expected) {
|
||||||
|
this.expected = expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by NMock to verfiy the ProgressUpdateEventArgs match the expected value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actualAsObject">Actual value to compare to the expected value</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the actual value matches the expected value; otherwise false
|
||||||
|
/// </returns>
|
||||||
|
public override bool Matches(object actualAsObject) {
|
||||||
|
ProgressReportEventArgs actual = (actualAsObject as ProgressReportEventArgs);
|
||||||
|
if(actual == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return (actual.Progress == this.expected.Progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Creates a string representation of the expected value</summary>
|
||||||
|
/// <param name="writer">Writer to write the string representation into</param>
|
||||||
|
public override void DescribeTo(TextWriter writer) {
|
||||||
|
writer.Write(this.expected.Progress.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Expected progress update event args value</summary>
|
||||||
|
private ProgressReportEventArgs expected;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class ProgressUpdateEventArgsMatcher
|
||||||
|
|
||||||
|
#region class TestTransaction
|
||||||
|
|
||||||
|
/// <summary>Transaction used for testing in this unit test</summary>
|
||||||
|
private class TestTransaction : Transaction, IProgressReporter {
|
||||||
|
|
||||||
|
/// <summary>will be triggered to report when progress has been achieved</summary>
|
||||||
|
public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged;
|
||||||
|
|
||||||
|
/// <summary>Changes the testing transaction's indicated progress</summary>
|
||||||
|
/// <param name="progress">
|
||||||
|
/// New progress to be reported by the testing transaction
|
||||||
|
/// </param>
|
||||||
|
public void ChangeProgress(float progress) {
|
||||||
|
OnAsyncProgressChanged(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Transitions the transaction into the ended state</summary>
|
||||||
|
public void End() {
|
||||||
|
OnAsyncEnded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Fires the progress update event</summary>
|
||||||
|
/// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Informs the observers of this transaction about the achieved progress.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual void OnAsyncProgressChanged(float progress) {
|
||||||
|
OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Fires the progress update event</summary>
|
||||||
|
/// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
|
||||||
|
EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged;
|
||||||
|
if(copy != null)
|
||||||
|
copy(this, eventArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class TestTransaction
|
||||||
|
|
||||||
|
/// <summary>Initialization routine executed before each test is run</summary>
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() {
|
||||||
|
this.mockery = new Mockery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Validates that the set transaction properly sums the progress</summary>
|
||||||
|
[Test]
|
||||||
|
public void TestSummedProgress() {
|
||||||
|
using(
|
||||||
|
TransactionGroup<TestTransaction> testTransactionGroup =
|
||||||
|
new TransactionGroup<TestTransaction>(
|
||||||
|
new TestTransaction[] { new TestTransaction(), new TestTransaction() }
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ITransactionGroupSubscriber mockedSubscriber = mockSubscriber(testTransactionGroup);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("ProgressChanged").
|
||||||
|
With(
|
||||||
|
new Matcher[] {
|
||||||
|
new NMock2.Matchers.TypeMatcher(typeof(TransactionGroup<TestTransaction>)),
|
||||||
|
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testTransactionGroup.Children[0].Transaction.ChangeProgress(0.5f);
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Validates that the transaction group respects the weights</summary>
|
||||||
|
[Test]
|
||||||
|
public void TestWeightedSummedProgress() {
|
||||||
|
using(
|
||||||
|
TransactionGroup<TestTransaction> testTransactionGroup =
|
||||||
|
new TransactionGroup<TestTransaction>(
|
||||||
|
new WeightedTransaction<TestTransaction>[] {
|
||||||
|
new WeightedTransaction<TestTransaction>(new TestTransaction(), 1.0f),
|
||||||
|
new WeightedTransaction<TestTransaction>(new TestTransaction(), 2.0f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ITransactionGroupSubscriber mockedSubscriber = mockSubscriber(testTransactionGroup);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("ProgressChanged").
|
||||||
|
With(
|
||||||
|
new Matcher[] {
|
||||||
|
new NMock2.Matchers.TypeMatcher(typeof(TransactionGroup<TestTransaction>)),
|
||||||
|
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f / 3.0f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testTransactionGroup.Children[0].Transaction.ChangeProgress(0.5f);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("ProgressChanged").
|
||||||
|
With(
|
||||||
|
new Matcher[] {
|
||||||
|
new NMock2.Matchers.TypeMatcher(typeof(TransactionGroup<TestTransaction>)),
|
||||||
|
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testTransactionGroup.Children[1].Transaction.ChangeProgress(0.5f);
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that the ended event is triggered when the last transaction ends
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestEndedEvent() {
|
||||||
|
using(
|
||||||
|
TransactionGroup<TestTransaction> testTransactionGroup =
|
||||||
|
new TransactionGroup<TestTransaction>(
|
||||||
|
new TestTransaction[] { new TestTransaction(), new TestTransaction() }
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ITransactionGroupSubscriber mockedSubscriber = mockSubscriber(testTransactionGroup);
|
||||||
|
|
||||||
|
Expect.Exactly(2).On(mockedSubscriber).
|
||||||
|
Method("ProgressChanged").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("Ended").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
|
testTransactionGroup.Children[0].Transaction.End();
|
||||||
|
testTransactionGroup.Children[1].Transaction.End();
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Mocks a subscriber for the events of a transaction</summary>
|
||||||
|
/// <param name="transaction">Transaction to mock an event subscriber for</param>
|
||||||
|
/// <returns>The mocked event subscriber</returns>
|
||||||
|
private ITransactionGroupSubscriber mockSubscriber(Transaction transaction) {
|
||||||
|
ITransactionGroupSubscriber mockedSubscriber =
|
||||||
|
this.mockery.NewMock<ITransactionGroupSubscriber>();
|
||||||
|
|
||||||
|
transaction.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
|
||||||
|
(transaction as IProgressReporter).AsyncProgressChanged +=
|
||||||
|
new EventHandler<ProgressReportEventArgs>(mockedSubscriber.ProgressChanged);
|
||||||
|
|
||||||
|
return mockedSubscriber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Mock object factory</summary>
|
||||||
|
private Mockery mockery;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
210
Source/Tracking/TransactionGroup.cs
Normal file
210
Source/Tracking/TransactionGroup.cs
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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 Nuclex.Support.Collections;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Forms a single transaction from a group of transactions</summary>
|
||||||
|
/// <typeparam name="TransactionType">Type of transactions to manage as a set</typeparam>
|
||||||
|
public class TransactionGroup<TransactionType> : Transaction, IDisposable, IProgressReporter
|
||||||
|
where TransactionType : Transaction {
|
||||||
|
|
||||||
|
/// <summary>will be triggered to report when progress has been achieved</summary>
|
||||||
|
public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged;
|
||||||
|
|
||||||
|
/// <summary>Initializes a new transaction group</summary>
|
||||||
|
/// <param name="childs">Transactions to track with this group</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Uses a default weighting factor of 1.0 for all transactions.
|
||||||
|
/// </remarks>
|
||||||
|
public TransactionGroup(IEnumerable<TransactionType> childs)
|
||||||
|
: this() {
|
||||||
|
|
||||||
|
// Construct a WeightedTransaction with the default weight for each
|
||||||
|
// transaction and wrap it in an ObservedTransaction
|
||||||
|
foreach(TransactionType transaction in childs) {
|
||||||
|
this.children.Add(
|
||||||
|
new ObservedWeightedTransaction<TransactionType>(
|
||||||
|
new WeightedTransaction<TransactionType>(transaction),
|
||||||
|
new ObservedWeightedTransaction<TransactionType>.ReportDelegate(
|
||||||
|
asyncProgressUpdated
|
||||||
|
),
|
||||||
|
new ObservedWeightedTransaction<TransactionType>.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)this.children.Count;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Initializes a new transaction group</summary>
|
||||||
|
/// <param name="childs">Transactions to track with this group</param>
|
||||||
|
public TransactionGroup(
|
||||||
|
IEnumerable<WeightedTransaction<TransactionType>> childs
|
||||||
|
)
|
||||||
|
: this() {
|
||||||
|
|
||||||
|
// Construct an ObservedTransaction around each of the WeightedTransactions
|
||||||
|
foreach(WeightedTransaction<TransactionType> transaction in childs) {
|
||||||
|
this.children.Add(
|
||||||
|
new ObservedWeightedTransaction<TransactionType>(
|
||||||
|
transaction,
|
||||||
|
new ObservedWeightedTransaction<TransactionType>.ReportDelegate(
|
||||||
|
asyncProgressUpdated
|
||||||
|
),
|
||||||
|
new ObservedWeightedTransaction<TransactionType>.ReportDelegate(
|
||||||
|
asyncChildEnded
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sum up the total weight
|
||||||
|
this.totalWeight += transaction.Weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Performs common initialization for the public constructors</summary>
|
||||||
|
private TransactionGroup() {
|
||||||
|
this.children = new List<ObservedWeightedTransaction<TransactionType>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Immediately releases all resources owned by the object</summary>
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Childs contained in the transaction set</summary>
|
||||||
|
public IList<WeightedTransaction<TransactionType>> 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<TransactionType>(
|
||||||
|
this.children
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.wrapper;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Fires the progress update event</summary>
|
||||||
|
/// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Informs the observers of this transactions about the achieved progress.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual void OnAsyncProgressChanged(float progress) {
|
||||||
|
OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Fires the progress update event</summary>
|
||||||
|
/// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
|
||||||
|
EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged;
|
||||||
|
if(copy != null)
|
||||||
|
copy(this, eventArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the progress of one of the observed transactions changes
|
||||||
|
/// </summary>
|
||||||
|
private void asyncProgressUpdated() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when an observed transaction ends
|
||||||
|
/// </summary>
|
||||||
|
private void asyncChildEnded() {
|
||||||
|
|
||||||
|
// 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
|
||||||
|
OnAsyncEnded();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Transactions being managed in the set</summary>
|
||||||
|
private List<ObservedWeightedTransaction<TransactionType>> children;
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper collection for exposing the child transactions under the
|
||||||
|
/// WeightedTransaction interface
|
||||||
|
/// </summary>
|
||||||
|
private volatile WeightedTransactionWrapperCollection<TransactionType> wrapper;
|
||||||
|
/// <summary>Summed weight of all transactions in the set</summary>
|
||||||
|
private float totalWeight;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
|
@ -1,257 +0,0 @@
|
||||||
#region CPL License
|
|
||||||
/*
|
|
||||||
Nuclex Framework
|
|
||||||
Copyright (C) 2002-2008 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;
|
|
||||||
|
|
||||||
#if UNITTEST
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NMock2;
|
|
||||||
|
|
||||||
namespace Nuclex.Support.Tracking {
|
|
||||||
|
|
||||||
/// <summary>Unit Test for the waitable group class</summary>
|
|
||||||
[TestFixture]
|
|
||||||
public class WaitableGroupTest {
|
|
||||||
|
|
||||||
#region interface IWaitableGroupSubscriber
|
|
||||||
|
|
||||||
/// <summary>Interface used to test the set waitable.</summary>
|
|
||||||
public interface IWaitableGroupSubscriber {
|
|
||||||
|
|
||||||
/// <summary>Called when the set waitable's progress changes</summary>
|
|
||||||
/// <param name="sender">Waitable group whose progress has changed</param>
|
|
||||||
/// <param name="e">Contains the new progress achieved</param>
|
|
||||||
void ProgressChanged(object sender, ProgressReportEventArgs e);
|
|
||||||
|
|
||||||
/// <summary>Called when the set waitable has ended</summary>
|
|
||||||
/// <param name="sender">Waitable group that as ended</param>
|
|
||||||
/// <param name="e">Not used</param>
|
|
||||||
void Ended(object sender, EventArgs e);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion // interface IWaitableGroupSubscriber
|
|
||||||
|
|
||||||
#region class ProgressUpdateEventArgsMatcher
|
|
||||||
|
|
||||||
/// <summary>Compares two ProgressUpdateEventArgsInstances for NMock validation</summary>
|
|
||||||
private class ProgressUpdateEventArgsMatcher : Matcher {
|
|
||||||
|
|
||||||
/// <summary>Initializes a new ProgressUpdateEventArgsMatcher </summary>
|
|
||||||
/// <param name="expected">Expected progress update event arguments</param>
|
|
||||||
public ProgressUpdateEventArgsMatcher(ProgressReportEventArgs expected) {
|
|
||||||
this.expected = expected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called by NMock to verfiy the ProgressUpdateEventArgs match the expected value
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actualAsObject">Actual value to compare to the expected value</param>
|
|
||||||
/// <returns>
|
|
||||||
/// True if the actual value matches the expected value; otherwise false
|
|
||||||
/// </returns>
|
|
||||||
public override bool Matches(object actualAsObject) {
|
|
||||||
ProgressReportEventArgs actual = (actualAsObject as ProgressReportEventArgs);
|
|
||||||
if(actual == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return (actual.Progress == this.expected.Progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Creates a string representation of the expected value</summary>
|
|
||||||
/// <param name="writer">Writer to write the string representation into</param>
|
|
||||||
public override void DescribeTo(TextWriter writer) {
|
|
||||||
writer.Write(this.expected.Progress.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Expected progress update event args value</summary>
|
|
||||||
private ProgressReportEventArgs expected;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion // class ProgressUpdateEventArgsMatcher
|
|
||||||
|
|
||||||
#region class TestWaitable
|
|
||||||
|
|
||||||
/// <summary>Waitable used for testing in this unit test</summary>
|
|
||||||
private class TestWaitable : Waitable, IProgressReporter {
|
|
||||||
|
|
||||||
/// <summary>will be triggered to report when progress has been achieved</summary>
|
|
||||||
public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged;
|
|
||||||
|
|
||||||
/// <summary>Changes the testing waitable's indicated progress</summary>
|
|
||||||
/// <param name="progress">
|
|
||||||
/// New progress to be reported by the testing waitable
|
|
||||||
/// </param>
|
|
||||||
public void ChangeProgress(float progress) {
|
|
||||||
OnAsyncProgressChanged(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Transitions the waitable into the ended state</summary>
|
|
||||||
public void End() {
|
|
||||||
OnAsyncEnded();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Fires the progress update event</summary>
|
|
||||||
/// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// Informs the observers of this waitable about the achieved progress.
|
|
||||||
/// </remarks>
|
|
||||||
protected virtual void OnAsyncProgressChanged(float progress) {
|
|
||||||
OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Fires the progress update event</summary>
|
|
||||||
/// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// Informs the observers of this waitable about the achieved progress.
|
|
||||||
/// Allows for classes derived from the Waitable class to easily provide
|
|
||||||
/// a custom event arguments class that has been derived from the
|
|
||||||
/// Waitable's ProgressUpdateEventArgs class.
|
|
||||||
/// </remarks>
|
|
||||||
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
|
|
||||||
EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged;
|
|
||||||
if(copy != null)
|
|
||||||
copy(this, eventArguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion // class TestWaitable
|
|
||||||
|
|
||||||
/// <summary>Initialization routine executed before each test is run</summary>
|
|
||||||
[SetUp]
|
|
||||||
public void Setup() {
|
|
||||||
this.mockery = new Mockery();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Validates that the set waitable properly sums the progress</summary>
|
|
||||||
[Test]
|
|
||||||
public void TestSummedProgress() {
|
|
||||||
WaitableGroup<TestWaitable> testWaitableGroup =
|
|
||||||
new WaitableGroup<TestWaitable>(
|
|
||||||
new TestWaitable[] { new TestWaitable(), new TestWaitable() }
|
|
||||||
);
|
|
||||||
|
|
||||||
IWaitableGroupSubscriber mockedSubscriber = mockSubscriber(testWaitableGroup);
|
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
|
||||||
Method("ProgressChanged").
|
|
||||||
With(
|
|
||||||
new Matcher[] {
|
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(WaitableGroup<TestWaitable>)),
|
|
||||||
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f))
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
testWaitableGroup.Children[0].Waitable.ChangeProgress(0.5f);
|
|
||||||
|
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Validates that the waitable group respects the weights</summary>
|
|
||||||
[Test]
|
|
||||||
public void TestWeightedSummedProgress() {
|
|
||||||
WaitableGroup<TestWaitable> testWaitableGroup =
|
|
||||||
new WaitableGroup<TestWaitable>(
|
|
||||||
new WeightedWaitable<TestWaitable>[] {
|
|
||||||
new WeightedWaitable<TestWaitable>(new TestWaitable(), 1.0f),
|
|
||||||
new WeightedWaitable<TestWaitable>(new TestWaitable(), 2.0f)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
IWaitableGroupSubscriber mockedSubscriber = mockSubscriber(testWaitableGroup);
|
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
|
||||||
Method("ProgressChanged").
|
|
||||||
With(
|
|
||||||
new Matcher[] {
|
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(WaitableGroup<TestWaitable>)),
|
|
||||||
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f / 3.0f))
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
testWaitableGroup.Children[0].Waitable.ChangeProgress(0.5f);
|
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
|
||||||
Method("ProgressChanged").
|
|
||||||
With(
|
|
||||||
new Matcher[] {
|
|
||||||
new NMock2.Matchers.TypeMatcher(typeof(WaitableGroup<TestWaitable>)),
|
|
||||||
new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f))
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
testWaitableGroup.Children[1].Waitable.ChangeProgress(0.5f);
|
|
||||||
|
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validates that the ended event is triggered when the last waitable ends
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void TestEndedEvent() {
|
|
||||||
WaitableGroup<TestWaitable> testWaitableGroup =
|
|
||||||
new WaitableGroup<TestWaitable>(
|
|
||||||
new TestWaitable[] { new TestWaitable(), new TestWaitable() }
|
|
||||||
);
|
|
||||||
|
|
||||||
IWaitableGroupSubscriber mockedSubscriber = mockSubscriber(testWaitableGroup);
|
|
||||||
|
|
||||||
Expect.Exactly(2).On(mockedSubscriber).
|
|
||||||
Method("ProgressChanged").
|
|
||||||
WithAnyArguments();
|
|
||||||
|
|
||||||
Expect.Once.On(mockedSubscriber).
|
|
||||||
Method("Ended").
|
|
||||||
WithAnyArguments();
|
|
||||||
|
|
||||||
testWaitableGroup.Children[0].Waitable.End();
|
|
||||||
testWaitableGroup.Children[1].Waitable.End();
|
|
||||||
|
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Mocks a subscriber for the events of a waitable</summary>
|
|
||||||
/// <param name="waitable">Waitable to mock an event subscriber for</param>
|
|
||||||
/// <returns>The mocked event subscriber</returns>
|
|
||||||
private IWaitableGroupSubscriber mockSubscriber(Waitable waitable) {
|
|
||||||
IWaitableGroupSubscriber mockedSubscriber =
|
|
||||||
this.mockery.NewMock<IWaitableGroupSubscriber>();
|
|
||||||
|
|
||||||
waitable.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
|
|
||||||
(waitable as IProgressReporter).AsyncProgressChanged +=
|
|
||||||
new EventHandler<ProgressReportEventArgs>(mockedSubscriber.ProgressChanged);
|
|
||||||
|
|
||||||
return mockedSubscriber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Mock object factory</summary>
|
|
||||||
private Mockery mockery;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Nuclex.Support.Tracking
|
|
||||||
|
|
||||||
#endif // UNITTEST
|
|
|
@ -1,202 +0,0 @@
|
||||||
#region CPL License
|
|
||||||
/*
|
|
||||||
Nuclex Framework
|
|
||||||
Copyright (C) 2002-2008 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 Nuclex.Support.Collections;
|
|
||||||
|
|
||||||
namespace Nuclex.Support.Tracking {
|
|
||||||
|
|
||||||
/// <summary>Forms a single waitable from a group of waitables</summary>
|
|
||||||
/// <typeparam name="WaitableType">Type of waitables to manage as a set</typeparam>
|
|
||||||
public class WaitableGroup<WaitableType> : Waitable, IDisposable, IProgressReporter
|
|
||||||
where WaitableType : Waitable {
|
|
||||||
|
|
||||||
/// <summary>will be triggered to report when progress has been achieved</summary>
|
|
||||||
public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged;
|
|
||||||
|
|
||||||
/// <summary>Initializes a new waitable group</summary>
|
|
||||||
/// <param name="childs">Waitables to track with this group</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// Uses a default weighting factor of 1.0 for all waitables.
|
|
||||||
/// </remarks>
|
|
||||||
public WaitableGroup(IEnumerable<WaitableType> childs)
|
|
||||||
: this() {
|
|
||||||
|
|
||||||
// Construct a WeightedWaitable with the default weight for each
|
|
||||||
// waitable and wrap it in an ObservedWaitable
|
|
||||||
foreach(WaitableType waitable in childs) {
|
|
||||||
this.children.Add(
|
|
||||||
new ObservedWeightedWaitable<WaitableType>(
|
|
||||||
new WeightedWaitable<WaitableType>(waitable),
|
|
||||||
new ObservedWeightedWaitable<WaitableType>.ReportDelegate(asyncProgressUpdated),
|
|
||||||
new ObservedWeightedWaitable<WaitableType>.ReportDelegate(asyncChildEnded)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since all waitables have a weight of 1.0, the total weight is
|
|
||||||
// equal to the number of waitables in our list
|
|
||||||
this.totalWeight = (float)this.children.Count;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Initializes a new waitable group</summary>
|
|
||||||
/// <param name="childs">Waitables to track with this group</param>
|
|
||||||
public WaitableGroup(
|
|
||||||
IEnumerable<WeightedWaitable<WaitableType>> childs
|
|
||||||
)
|
|
||||||
: this() {
|
|
||||||
|
|
||||||
// Construct an ObservedWaitable around each of the WeightedWaitables
|
|
||||||
foreach(WeightedWaitable<WaitableType> waitable in childs) {
|
|
||||||
this.children.Add(
|
|
||||||
new ObservedWeightedWaitable<WaitableType>(
|
|
||||||
waitable,
|
|
||||||
new ObservedWeightedWaitable<WaitableType>.ReportDelegate(asyncProgressUpdated),
|
|
||||||
new ObservedWeightedWaitable<WaitableType>.ReportDelegate(asyncChildEnded)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sum up the total weight
|
|
||||||
this.totalWeight += waitable.Weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Performs common initialization for the public constructors</summary>
|
|
||||||
private WaitableGroup() {
|
|
||||||
this.children = new List<ObservedWeightedWaitable<WaitableType>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Immediately releases all resources owned by the object</summary>
|
|
||||||
public void Dispose() {
|
|
||||||
|
|
||||||
if(this.children != null) {
|
|
||||||
|
|
||||||
// Dispose all the observed waitables, disconnecting the events from the
|
|
||||||
// actual waitables 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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Childs contained in the waitable set</summary>
|
|
||||||
public IList<WeightedWaitable<WaitableType>> Children {
|
|
||||||
get {
|
|
||||||
|
|
||||||
// The wrapper is constructed only when needed. Most of the time, users will
|
|
||||||
// just create a waitable 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 WeightedWaitableWrapperCollection<WaitableType>(
|
|
||||||
this.children
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.wrapper;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Fires the progress update event</summary>
|
|
||||||
/// <param name="progress">Progress to report (ranging from 0.0 to 1.0)</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// Informs the observers of this waitables about the achieved progress.
|
|
||||||
/// </remarks>
|
|
||||||
protected virtual void OnAsyncProgressChanged(float progress) {
|
|
||||||
OnAsyncProgressChanged(new ProgressReportEventArgs(progress));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Fires the progress update event</summary>
|
|
||||||
/// <param name="eventArguments">Progress to report (ranging from 0.0 to 1.0)</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// Informs the observers of this waitable about the achieved progress.
|
|
||||||
/// Allows for classes derived from the Waitable class to easily provide
|
|
||||||
/// a custom event arguments class that has been derived from the
|
|
||||||
/// waitable's ProgressUpdateEventArgs class.
|
|
||||||
/// </remarks>
|
|
||||||
protected virtual void OnAsyncProgressChanged(ProgressReportEventArgs eventArguments) {
|
|
||||||
EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged;
|
|
||||||
if(copy != null)
|
|
||||||
copy(this, eventArguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the progress of one of the observed waitables changes
|
|
||||||
/// </summary>
|
|
||||||
private void asyncProgressUpdated() {
|
|
||||||
float totalProgress = 0.0f;
|
|
||||||
|
|
||||||
// Calculate the sum of the progress reported by our child waitables,
|
|
||||||
// scaled to the weight each waitable has assigned to it.
|
|
||||||
for(int index = 0; index < this.children.Count; ++index) {
|
|
||||||
totalProgress +=
|
|
||||||
this.children[index].Progress * this.children[index].WeightedWaitable.Weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the actual combined progress
|
|
||||||
if(this.totalWeight > 0.0f)
|
|
||||||
totalProgress /= this.totalWeight;
|
|
||||||
|
|
||||||
// Send out the progress update
|
|
||||||
OnAsyncProgressChanged(totalProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when an observed waitable ends
|
|
||||||
/// </summary>
|
|
||||||
private void asyncChildEnded() {
|
|
||||||
|
|
||||||
// If there's still at least one waitable going, don't report that
|
|
||||||
// the waitable group has finished yet.
|
|
||||||
for(int index = 0; index < this.children.Count; ++index)
|
|
||||||
if(!this.children[index].WeightedWaitable.Waitable.Ended)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// All child waitables have ended, so the set has now ended as well
|
|
||||||
OnAsyncEnded();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Waitables being managed in the set</summary>
|
|
||||||
private List<ObservedWeightedWaitable<WaitableType>> children;
|
|
||||||
/// <summary>
|
|
||||||
/// Wrapper collection for exposing the child waitables under the
|
|
||||||
/// WeightedWaitable interface
|
|
||||||
/// </summary>
|
|
||||||
private volatile WeightedWaitableWrapperCollection<WaitableType> wrapper;
|
|
||||||
/// <summary>Summed weight of all waitables in the set</summary>
|
|
||||||
private float totalWeight;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Nuclex.Support.Tracking
|
|
88
Source/Tracking/WeightedTransaction.Test.cs
Normal file
88
Source/Tracking/WeightedTransaction.Test.cs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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;
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.SyntaxHelpers;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the weighted transaction wrapper</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class WeightedTransactionTest {
|
||||||
|
|
||||||
|
#region class TestTransaction
|
||||||
|
|
||||||
|
/// <summary>Transaction used for testing in this unit test</summary>
|
||||||
|
private class TestTransaction : Transaction { }
|
||||||
|
|
||||||
|
#endregion // class TestTransaction
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the weighted transaction wrapper correctly stores the transaction
|
||||||
|
/// it was given in the constructor
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestTransactionStorage() {
|
||||||
|
TestTransaction transaction = new TestTransaction();
|
||||||
|
WeightedTransaction<Transaction> testWrapper = new WeightedTransaction<Transaction>(
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.AreSame(transaction, testWrapper.Transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the weighted transaction wrapper correctly applies the default
|
||||||
|
/// unit weight to the transaction if no explicit weight was specified
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestDefaultWeight() {
|
||||||
|
TestTransaction transaction = new TestTransaction();
|
||||||
|
WeightedTransaction<Transaction> testWrapper = new WeightedTransaction<Transaction>(
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.AreEqual(1.0f, testWrapper.Weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the weighted transaction wrapper correctly stores the weight
|
||||||
|
/// it was given in the constructor
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestWeightStorage() {
|
||||||
|
TestTransaction transaction = new TestTransaction();
|
||||||
|
WeightedTransaction<Transaction> testWrapper = new WeightedTransaction<Transaction>(
|
||||||
|
transaction, 12.0f
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.AreEqual(12.0f, testWrapper.Weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
60
Source/Tracking/WeightedTransaction.cs
Normal file
60
Source/Tracking/WeightedTransaction.cs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#region CPL License
|
||||||
|
/*
|
||||||
|
Nuclex Framework
|
||||||
|
Copyright (C) 2002-2008 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 {
|
||||||
|
|
||||||
|
/// <summary>Transaction with an associated weight for the total progress</summary>
|
||||||
|
public class WeightedTransaction<TransactionType> where TransactionType : Transaction {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new weighted transaction with a default weight of 1.0
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="transaction">Transaction whose progress to monitor</param>
|
||||||
|
public WeightedTransaction(TransactionType transaction) : this(transaction, 1.0f) { }
|
||||||
|
|
||||||
|
/// <summary>Initializes a new weighted transaction</summary>
|
||||||
|
/// <param name="transaction">transaction whose progress to monitor</param>
|
||||||
|
/// <param name="weight">Weighting of the transaction's progress</param>
|
||||||
|
public WeightedTransaction(TransactionType transaction, float weight) {
|
||||||
|
this.transaction = transaction;
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Transaction being wrapped by this weighted transaction</summary>
|
||||||
|
public TransactionType Transaction {
|
||||||
|
get { return this.transaction; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The contribution of this transaction to the total progress</summary>
|
||||||
|
public float Weight {
|
||||||
|
get { return this.weight; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Transaction whose progress we're tracking</summary>
|
||||||
|
private TransactionType transaction;
|
||||||
|
/// <summary>Weighting of this transaction in the total progress</summary>
|
||||||
|
private float weight;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
|
@ -1,60 +0,0 @@
|
||||||
#region CPL License
|
|
||||||
/*
|
|
||||||
Nuclex Framework
|
|
||||||
Copyright (C) 2002-2008 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 {
|
|
||||||
|
|
||||||
/// <summary>Waitable with an associated weight for the total progress</summary>
|
|
||||||
public class WeightedWaitable<WaitableType> where WaitableType : Waitable {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new weighted waitable with a default weight of 1.0
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="waitable">Waitable whose progress to monitor</param>
|
|
||||||
public WeightedWaitable(WaitableType waitable) : this(waitable, 1.0f) { }
|
|
||||||
|
|
||||||
/// <summary>Initializes a new weighted waitable</summary>
|
|
||||||
/// <param name="waitable">Waitable whose progress to monitor</param>
|
|
||||||
/// <param name="weight">Weighting of the waitable's progress</param>
|
|
||||||
public WeightedWaitable(WaitableType waitable, float weight) {
|
|
||||||
this.waitable = waitable;
|
|
||||||
this.weight = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Waitable being wrapped by this weighted waitable</summary>
|
|
||||||
public WaitableType Waitable {
|
|
||||||
get { return this.waitable; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>The contribution of this waitable to the total progress</summary>
|
|
||||||
public float Weight {
|
|
||||||
get { return this.weight; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Waitable whose progress we're tracking</summary>
|
|
||||||
private WaitableType waitable;
|
|
||||||
/// <summary>Weighting of this waitable in the total progress</summary>
|
|
||||||
private float weight;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Nuclex.Support.Tracking
|
|
Loading…
Reference in New Issue
Block a user