SetProgression fully working and tested, yay!; remedied a little imperfection that would theoretically allow progressions to trigger the end even twice

git-svn-id: file:///srv/devel/repo-conversion/nusu@13 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2007-04-20 18:04:19 +00:00
parent 71b6dc90a2
commit 0008304d64
4 changed files with 44 additions and 19 deletions

View File

@ -40,7 +40,7 @@ namespace Nuclex.Support.Tracking {
/// <summary>Immediately releases all resources owned by the object</summary> /// <summary>Immediately releases all resources owned by the object</summary>
public void Dispose() { public void Dispose() {
disconnectEvents(); asyncDisconnectEvents();
} }
/// <summary>Weighted progression being observed</summary> /// <summary>Weighted progression being observed</summary>
@ -57,11 +57,15 @@ namespace Nuclex.Support.Tracking {
/// <param name="sender">Progression that has ended</param> /// <param name="sender">Progression that has ended</param>
/// <param name="e">Not used</param> /// <param name="e">Not used</param>
private void asyncEnded(object sender, EventArgs e) { private void asyncEnded(object sender, EventArgs e) {
ReportDelegate endedCallback = this.endedCallback;
ReportDelegate progressUpdateCallback = this.progressUpdateCallback;
asyncDisconnectEvents(); // We don't need those anymore!
this.progress = 1.0f; this.progress = 1.0f;
progressUpdateCallback();
disconnectEvents(); // We don't need those anymore! endedCallback();
this.progressUpdateCallback();
} }
/// <summary>Called when the progress of the observed progression changes</summary> /// <summary>Called when the progress of the observed progression changes</summary>
@ -74,7 +78,7 @@ namespace Nuclex.Support.Tracking {
} }
/// <summary>Unscribes from all events of the observed progression</summary> /// <summary>Unscribes from all events of the observed progression</summary>
private void disconnectEvents() { private void asyncDisconnectEvents() {
// Make use of the double check locking idiom to avoid the costly lock when // Make use of the double check locking idiom to avoid the costly lock when
// the events have already been unsubscribed // the events have already been unsubscribed

View File

@ -114,15 +114,18 @@ namespace Nuclex.Support.Tracking {
/// </remarks> /// </remarks>
protected virtual void OnAsyncEnded() { protected virtual void OnAsyncEnded() {
// TODO: Find a way around this. Interlocked.Exchange would be best! // Make sure the progression is not ended more than once. By guaranteeing that
// We do not lock here since we require this method to be called only once // a progression can only be ended once, we allow users of this class to
// in the object's lifetime. If someone really badly wanted to break this // skip some safeguards against notifications arriving twice.
// he'd probably have a one-in-a-million chance of getting through. lock(this.syncRoot) {
if(this.ended) if(this.ended)
throw new InvalidOperationException("The progression has already been ended"); throw new InvalidOperationException("The progression has already been ended");
this.ended = true; this.ended = true;
}
// Doesn't need a lock. If another thread wins the race and creates the event // Doesn't need a lock. If another thread wins the race and creates the event
// after we just saw it being null, it would be created in an already set // after we just saw it being null, it would be created in an already set
// state due to the ended flag (see above) being set to true beforehand! // state due to the ended flag (see above) being set to true beforehand!

View File

@ -104,9 +104,7 @@ namespace Nuclex.Support.Tracking {
public void TestSummedProgress() { public void TestSummedProgress() {
SetProgression<TestProgression> testSetProgression = SetProgression<TestProgression> testSetProgression =
new SetProgression<TestProgression>( new SetProgression<TestProgression>(
new TestProgression[] { new TestProgression[] { new TestProgression(), new TestProgression() }
new TestProgression(), new TestProgression()
}
); );
ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression); ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression);
@ -184,9 +182,7 @@ namespace Nuclex.Support.Tracking {
public void TestEndedEvent() { public void TestEndedEvent() {
SetProgression<TestProgression> testSetProgression = SetProgression<TestProgression> testSetProgression =
new SetProgression<TestProgression>( new SetProgression<TestProgression>(
new TestProgression[] { new TestProgression[] { new TestProgression(), new TestProgression() }
new TestProgression(), new TestProgression()
}
); );
ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression); ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression);

View File

@ -8,14 +8,16 @@ namespace Nuclex.Support.Tracking {
/// <summary>Forms a single progression from a set of progressions</summary> /// <summary>Forms a single progression from a set of progressions</summary>
/// <typeparam name="ProgressionType">Type of progressions to manage as a set</typeparam> /// <typeparam name="ProgressionType">Type of progressions to manage as a set</typeparam>
public class SetProgression<ProgressionType> : Progression public class SetProgression<ProgressionType> : Progression, IDisposable
where ProgressionType : Progression { where ProgressionType : Progression {
/// <summary>Performs common initialization for the public constructors</summary> /// <summary>Performs common initialization for the public constructors</summary>
private SetProgression() { private SetProgression() {
this.childs = new List<ObservedProgression<ProgressionType>>(); this.childs = new List<ObservedProgression<ProgressionType>>();
this.asyncProgressUpdatedDelegate = this.asyncProgressUpdatedDelegate =
new ObservedProgression<ProgressionType>.ReportDelegate(asyncProgressUpdated); new ObservedProgression<ProgressionType>.ReportDelegate(asyncProgressUpdated);
this.asyncEndedDelegate = this.asyncEndedDelegate =
new ObservedProgression<ProgressionType>.ReportDelegate(asyncEnded); new ObservedProgression<ProgressionType>.ReportDelegate(asyncEnded);
} }
@ -71,6 +73,23 @@ namespace Nuclex.Support.Tracking {
} }
/// <summary>Immediately releases all resources owned by the object</summary>
public void Dispose() {
if(this.childs != null) {
// Dispose all the observed progressions, disconnecting the events from the
// actual progressions so the GC can more easily collect this class
for(int index = 0; index < this.childs.Count; ++index)
this.childs[index].Dispose();
this.childs = null;
this.wrapper = null;
}
}
/// <summary>Childs contained in the progression set</summary> /// <summary>Childs contained in the progression set</summary>
public IList<WeightedProgression<ProgressionType>> Childs { public IList<WeightedProgression<ProgressionType>> Childs {
get { get {
@ -120,10 +139,13 @@ namespace Nuclex.Support.Tracking {
/// </summary> /// </summary>
private void asyncEnded() { private void asyncEnded() {
// If there's still at least one progression going, don't report that
// the SetProgression has finished yet.
for(int index = 0; index < this.childs.Count; ++index) for(int index = 0; index < this.childs.Count; ++index)
if(!this.childs[index].WeightedProgression.Progression.Ended) if(!this.childs[index].WeightedProgression.Progression.Ended)
return; return;
// All child progressions have ended, so the set has now ended as well
OnAsyncEnded(); OnAsyncEnded();
} }