1ae0c7de63
git-svn-id: file:///srv/devel/repo-conversion/nusu@31 d2e56fa2-650e-0410-a79f-9358c0239efd
254 lines
9.7 KiB
C#
254 lines
9.7 KiB
C#
#region CPL License
|
|
/*
|
|
Nuclex Framework
|
|
Copyright (C) 2002-2007 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.Threading;
|
|
|
|
namespace Nuclex.Support.Tracking {
|
|
|
|
/// <summary>Base class for actions that give an indication of their progress</summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// By encapsulating long-running operations which will ideally be running in
|
|
/// a background thread in a class that's derived from Progression you can wait
|
|
/// for the completion of the operation and receive 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 waits for the operation to
|
|
/// complete. It is also possible to register callbacks which will be fired once
|
|
/// the progression has ended.
|
|
/// </para>
|
|
/// <para>
|
|
/// This class deliberately does not provide an Execute() method or anything similar
|
|
/// to clearly seperate the initiation of an operation from just monitoring it.
|
|
/// By omitting an Execute() method, it also becomes possible to construct a
|
|
/// progression just-in-time when it is explicitely asked for.
|
|
/// </para>
|
|
/// </remarks>
|
|
public abstract class Progression {
|
|
|
|
#region class EndedDummyProgression
|
|
|
|
/// <summary>Dummy progression which always is in the 'ended' state</summary>
|
|
private class EndedDummyProgression : Progression {
|
|
|
|
/// <summary>Initializes a new ended dummy progression</summary>
|
|
public EndedDummyProgression() {
|
|
#if PROGRESSION_STARTABLE
|
|
OnAsyncStarted();
|
|
#endif
|
|
OnAsyncEnded();
|
|
}
|
|
|
|
}
|
|
|
|
#endregion // class EndedDummyProgression
|
|
|
|
/// <summary>A dummy progression that's always in the 'ended' state</summary>
|
|
/// <remarks>
|
|
/// Useful if an operation is already complete when it's being asked for or
|
|
/// when a progression that's lazily created is accessed after the original
|
|
/// operation has ended already.
|
|
/// </remarks>
|
|
public static readonly Progression EndedDummy = new EndedDummyProgression();
|
|
|
|
/// <summary>will be triggered to report when progress has been achieved</summary>
|
|
public event EventHandler<ProgressUpdateEventArgs> AsyncProgressUpdated;
|
|
|
|
#if PROGRESSION_STARTABLE
|
|
/// <summary>Will be triggered when the progression has ended</summary>
|
|
public event EventHandler AsyncStarted;
|
|
#endif
|
|
|
|
/// <summary>Will be triggered when the progression has ended</summary>
|
|
public event EventHandler AsyncEnded;
|
|
|
|
#if PROGRESSION_STARTABLE
|
|
/// <summary>True when the progression has been started.</summary>
|
|
/// <remarks>
|
|
/// This is also true if the progression has been started but is already finished.
|
|
/// To find out whether the progression is running just now, use the IsRunning
|
|
/// property instead.
|
|
/// </remarks>
|
|
public bool Started {
|
|
get { return this.started; }
|
|
}
|
|
#endif
|
|
|
|
/// <summary>Whether the progression has ended already</summary>
|
|
public bool Ended {
|
|
get { return this.ended; }
|
|
}
|
|
|
|
#if PROGRESSION_STARTABLE
|
|
/// <summary>Whether the progression is currently executing.</summary>
|
|
/// <remarks>
|
|
/// This property is true when the progression is executing right at the time of
|
|
/// the call (eg. when it has been started and has not ended yet).
|
|
/// </remarks>
|
|
public bool IsRunning {
|
|
get { return (this.started && !base.Ended); }
|
|
}
|
|
#endif
|
|
|
|
/// <summary>WaitHandle that can be used to wait for the progression to end</summary>
|
|
public WaitHandle WaitHandle {
|
|
get {
|
|
|
|
// The WaitHandle will only be created when someone asks for it!
|
|
// See the Double-Check Locking idiom on why the condition is checked twice
|
|
// (primarily, it avoids an expensive lock when it isn't needed)
|
|
//
|
|
// We can *not* optimize this lock away since we absolutely must not create
|
|
// two doneEvents -- someone might call .WaitOne() on the first one when only
|
|
// the second one is assigned to this.doneEvent and thus gets set in the end.
|
|
if(this.doneEvent == null) {
|
|
|
|
lock(this) {
|
|
|
|
if(this.doneEvent == null)
|
|
this.doneEvent = new ManualResetEvent(this.ended);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.doneEvent;
|
|
}
|
|
}
|
|
|
|
/// <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 progression about the achieved progress.
|
|
/// </remarks>
|
|
protected virtual void OnAsyncProgressUpdated(float progress) {
|
|
OnAsyncProgressUpdated(new ProgressUpdateEventArgs(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 progression about the achieved progress.
|
|
/// Allows for classes derived from the Progression class to easily provide
|
|
/// a custom event arguments class that has been derived from the
|
|
/// Progression's ProgressUpdateEventArgs class.
|
|
/// </remarks>
|
|
protected virtual void OnAsyncProgressUpdated(ProgressUpdateEventArgs eventArguments) {
|
|
EventHandler<ProgressUpdateEventArgs> copy = AsyncProgressUpdated;
|
|
if(copy != null)
|
|
copy(this, eventArguments);
|
|
}
|
|
|
|
#if PROGRESSION_STARTABLE
|
|
/// <summary>Fires the AsyncStarted event</summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This event should be fired once when the implementing class begins its
|
|
/// work to indicate any observers that the process is now running.
|
|
/// </para>
|
|
/// <para>
|
|
/// Calling this method is mandatory. Care should be taken that it is called
|
|
/// exactly once and that it is called before OnAsyncEnded().
|
|
/// </para>
|
|
/// </remarks>
|
|
protected virtual void OnAsyncStarted() {
|
|
|
|
// Make sure that the progression is not started more than once.
|
|
lock(this) {
|
|
|
|
// No double lock here as it would be an implementation fault if this exception
|
|
// should be triggered. There's no sense in sacrificing normal runtime speed
|
|
// for improved performance in a case that should never occur in the first place.
|
|
if(this.started)
|
|
throw new InvalidOperationException("The operation has already been started");
|
|
|
|
this.started = true;
|
|
|
|
}
|
|
|
|
// Fire the AsyncStarted event
|
|
EventHandler copy = AsyncStarted;
|
|
if(copy != null)
|
|
copy(this, EventArgs.Empty);
|
|
|
|
}
|
|
#endif
|
|
|
|
/// <summary>Fires the AsyncEnded event</summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This event should be fired by the implementing class when its work is completed.
|
|
/// It's of no interest to this class whether the outcome of the process was
|
|
/// successfull or not, the outcome and results of the process taking place both
|
|
/// need to be communicated seperately.
|
|
/// </para>
|
|
/// <para>
|
|
/// Calling this method is mandatory. Implementers need to take care that
|
|
/// the OnAsyncEnded() method is called on any instance of Progression that's
|
|
/// being created. This method also must not be called more than once.
|
|
/// </para>
|
|
/// </remarks>
|
|
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) {
|
|
|
|
// No double lock here, this is an exception that indicates an implementation
|
|
// error that will not be triggered under normal circumstances. We don't want
|
|
// to waste any effort optimizing the speed at which an implementation fault
|
|
// will be noticed.
|
|
if(this.ended)
|
|
throw new InvalidOperationException("The progression has already been ended");
|
|
|
|
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
|
|
// state due to the ended flag (see above) being set to true beforehand!
|
|
if(this.doneEvent != null)
|
|
this.doneEvent.Set();
|
|
|
|
// Finally, fire the AsyncEnded event
|
|
EventHandler copy = AsyncEnded;
|
|
if(copy != null)
|
|
copy(this, EventArgs.Empty);
|
|
|
|
}
|
|
|
|
/// <summary>Event that will be set when the progression is completed</summary>
|
|
/// <remarks>
|
|
/// This event is will only be created when it is specifically asked for using
|
|
/// the WaitHandle property.
|
|
/// </remarks>
|
|
private volatile ManualResetEvent doneEvent;
|
|
#if PROGRESSION_STARTABLE
|
|
/// <summary>Whether the operation has been started yet</summary>
|
|
private volatile bool started;
|
|
#endif
|
|
/// <summary>Whether the operation has completed yet</summary>
|
|
private volatile bool ended;
|
|
|
|
}
|
|
|
|
} // namespace Nuclex.Support.Tracking
|