#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 { /// Waitable being observed by another object /// /// Type of the waitable that is being observed /// internal class ObservedWeightedWaitable : IDisposable where WaitableType : Waitable { /// Delegate for reporting progress updates public delegate void ReportDelegate(); /// Initializes a new observed waitable /// Weighted waitable being observed /// /// Callback to invoke when the waitable's progress changes /// /// /// Callback to invoke when the waitable has ended /// internal ObservedWeightedWaitable( WeightedWaitable 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( asyncProgressChanged ); this.progressReporter.AsyncProgressChanged += this.asyncProgressChangedEventHandler; } } } } /// Immediately releases all resources owned by the object public void Dispose() { asyncDisconnectEvents(); } /// Weighted waitable being observed public WeightedWaitable WeightedWaitable { get { return this.weightedWaitable; } } /// Amount of progress this waitable has achieved so far public float Progress { get { return this.progress; } } /// Called when the observed waitable has ended /// Waitable 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! // 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(); } /// Called when the progress of the observed waitable changes /// Waitable whose progress has changed /// Contains the updated progress private void asyncProgressChanged(object sender, ProgressReportEventArgs e) { this.progress = e.Progress; this.progressUpdateCallback(); } /// Unsubscribes from all events of the observed waitable 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 asyncProgressChangedEventHandler; /// The observed waitable's progress reporting interface private IProgressReporter progressReporter; /// The weighted wable that is being observed private WeightedWaitable weightedWaitable; /// Callback to invoke when the progress updates private volatile ReportDelegate progressUpdateCallback; /// Callback to invoke when the waitable ends private volatile ReportDelegate endedCallback; /// Progress achieved so far private volatile float progress; } } // namespace Nuclex.Support.Tracking