Nuclex.Support/Source/Tracking/Internal/ObservedWeightedWaitable.cs

178 lines
6.7 KiB
C#
Raw Normal View History

#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