diff --git a/Source/Tracking/Internal/ObservedProgression.cs b/Source/Tracking/Internal/ObservedProgression.cs index aab0d8d..2906387 100644 --- a/Source/Tracking/Internal/ObservedProgression.cs +++ b/Source/Tracking/Internal/ObservedProgression.cs @@ -40,7 +40,7 @@ namespace Nuclex.Support.Tracking { /// Immediately releases all resources owned by the object public void Dispose() { - disconnectEvents(); + asyncDisconnectEvents(); } /// Weighted progression being observed @@ -57,11 +57,15 @@ namespace Nuclex.Support.Tracking { /// Progression that has ended /// Not used 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; + progressUpdateCallback(); - disconnectEvents(); // We don't need those anymore! - - this.progressUpdateCallback(); + endedCallback(); } /// Called when the progress of the observed progression changes @@ -74,7 +78,7 @@ namespace Nuclex.Support.Tracking { } /// Unscribes from all events of the observed progression - private void disconnectEvents() { + private void asyncDisconnectEvents() { // Make use of the double check locking idiom to avoid the costly lock when // the events have already been unsubscribed diff --git a/Source/Tracking/Progression.cs b/Source/Tracking/Progression.cs index 05a20cd..b8c24c0 100644 --- a/Source/Tracking/Progression.cs +++ b/Source/Tracking/Progression.cs @@ -113,15 +113,18 @@ namespace Nuclex.Support.Tracking { /// seperately. /// protected virtual void OnAsyncEnded() { + + // Make sure the progression is not ended more than once. By guaranteeing that + // a progression can only be ended once, we allow users of this class to + // skip some safeguards against notifications arriving twice. + lock(this.syncRoot) { - // TODO: Find a way around this. Interlocked.Exchange would be best! - // We do not lock here since we require this method to be called only once - // in the object's lifetime. If someone really badly wanted to break this - // he'd probably have a one-in-a-million chance of getting through. - if(this.ended) - throw new InvalidOperationException("The progression has already been ended"); + if(this.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 // after we just saw it being null, it would be created in an already set diff --git a/Source/Tracking/SetProgression.Test.cs b/Source/Tracking/SetProgression.Test.cs index f39658f..2d014f7 100644 --- a/Source/Tracking/SetProgression.Test.cs +++ b/Source/Tracking/SetProgression.Test.cs @@ -104,9 +104,7 @@ namespace Nuclex.Support.Tracking { public void TestSummedProgress() { SetProgression testSetProgression = new SetProgression( - new TestProgression[] { - new TestProgression(), new TestProgression() - } + new TestProgression[] { new TestProgression(), new TestProgression() } ); ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression); @@ -184,9 +182,7 @@ namespace Nuclex.Support.Tracking { public void TestEndedEvent() { SetProgression testSetProgression = new SetProgression( - new TestProgression[] { - new TestProgression(), new TestProgression() - } + new TestProgression[] { new TestProgression(), new TestProgression() } ); ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression); diff --git a/Source/Tracking/SetProgression.cs b/Source/Tracking/SetProgression.cs index c2ce6da..bc0f2c9 100644 --- a/Source/Tracking/SetProgression.cs +++ b/Source/Tracking/SetProgression.cs @@ -8,14 +8,16 @@ namespace Nuclex.Support.Tracking { /// Forms a single progression from a set of progressions /// Type of progressions to manage as a set - public class SetProgression : Progression + public class SetProgression : Progression, IDisposable where ProgressionType : Progression { /// Performs common initialization for the public constructors private SetProgression() { this.childs = new List>(); + this.asyncProgressUpdatedDelegate = new ObservedProgression.ReportDelegate(asyncProgressUpdated); + this.asyncEndedDelegate = new ObservedProgression.ReportDelegate(asyncEnded); } @@ -71,6 +73,23 @@ namespace Nuclex.Support.Tracking { } + /// Immediately releases all resources owned by the object + 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; + + } + + } + /// Childs contained in the progression set public IList> Childs { get { @@ -120,10 +139,13 @@ namespace Nuclex.Support.Tracking { /// 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) if(!this.childs[index].WeightedProgression.Progression.Ended) return; + // All child progressions have ended, so the set has now ended as well OnAsyncEnded(); }