diff --git a/Nuclex.Support (x86).csproj b/Nuclex.Support (x86).csproj index c5fcf5c..dbee5ed 100644 --- a/Nuclex.Support (x86).csproj +++ b/Nuclex.Support (x86).csproj @@ -60,6 +60,8 @@ False + + @@ -146,6 +148,7 @@ + diff --git a/Source/Scheduling/Operation.cs b/Source/Scheduling/Operation.cs index 582fdb2..ceaadac 100644 --- a/Source/Scheduling/Operation.cs +++ b/Source/Scheduling/Operation.cs @@ -26,64 +26,10 @@ using Nuclex.Support.Tracking; namespace Nuclex.Support.Scheduling { /// Base class for observable operations running in the background - public abstract class Operation : Progression { + public abstract class Operation : FailableProgression { /// Launches the background operation - public abstract void Begin(); - - /// Waits for the background operation to end - /// - /// Any exceptions raised in the background operation will be thrown - /// in this method. If you decide to override this method, you should - /// call End() first (and let any possible exception through to your - /// caller). - /// - public virtual void End() { - - // By design, end can only be called once! - lock(this) { - if(this.endCalled) - throw new InvalidOperationException("End() has already been called"); - - this.endCalled = true; - } - - // If the progression itself hasn't ended yet, block the caller until it has. - if(!Ended) - WaitHandle.WaitOne(); - - // If an exception occured during the background execution - if(this.occuredException != null) - throw this.occuredException; - - } - - /// Exception that occured while the operation was executing - /// - /// If this field is null, it is assumed that no exception has occured - /// in the background process. If it is set, however, the End() method will - /// re-raise the exception to the calling thread when it is called. - /// - public Exception OccuredException { - get { return this.occuredException; } - } - - /// Sets the exception to raise to the caller of the End() method - /// Exception to raise to the caller of the End() method - protected void SetException(Exception exception) { - - // We allow the caller to set the exception multiple times. While I certainly - // can't think of a scenario where this would happen, throwing an exception - // in that case seems worse. The caller might just be executing an exception - // handling block and locking + throwing here could cause all kinds of problems. - this.occuredException = exception; - - } - - /// Exception that occured while the operation was executing - private volatile Exception occuredException; - /// Whether the End() method has been called already - private volatile bool endCalled; + public abstract void Start(); } diff --git a/Source/Scheduling/QueueOperation.Test.cs b/Source/Scheduling/QueueOperation.Test.cs index 88bf4eb..403c1f1 100644 --- a/Source/Scheduling/QueueOperation.Test.cs +++ b/Source/Scheduling/QueueOperation.Test.cs @@ -99,7 +99,7 @@ namespace Nuclex.Support.Scheduling { private class TestOperation : Operation { /// Begins executing the operation. Yeah, sure :) - public override void Begin() { } + public override void Start() { } /// Moves the operation into the ended state public void SetEnded() { @@ -144,7 +144,7 @@ namespace Nuclex.Support.Scheduling { IQueueOperationSubscriber mockedSubscriber = mockSubscriber(testQueueOperation); - testQueueOperation.Begin(); + testQueueOperation.Start(); Expect.Once.On(mockedSubscriber). Method("ProgressUpdated"). diff --git a/Source/Scheduling/QueueOperation.cs b/Source/Scheduling/QueueOperation.cs index 55c6ca8..5b91eff 100644 --- a/Source/Scheduling/QueueOperation.cs +++ b/Source/Scheduling/QueueOperation.cs @@ -80,8 +80,8 @@ namespace Nuclex.Support.Scheduling { } /// Launches the background operation - public override void Begin() { - beginCurrentOperation(); + public override void Start() { + startCurrentOperation(); } /// Prepares the current operation and calls its Begin() method @@ -89,13 +89,13 @@ namespace Nuclex.Support.Scheduling { /// This subscribes the queue to the events of to the current operation /// and launches the operation by calling its Begin() method. /// - private void beginCurrentOperation() { + private void startCurrentOperation() { OperationType operation = this.children[this.currentOperationIndex].Progression; operation.AsyncEnded += this.asyncOperationEndedDelegate; operation.AsyncProgressUpdated += this.asyncOperationProgressUpdatedDelegate; - operation.Begin(); + operation.Start(); } /// Disconnects from the current operation and calls its End() method @@ -112,7 +112,7 @@ namespace Nuclex.Support.Scheduling { operation.AsyncProgressUpdated -= this.asyncOperationProgressUpdatedDelegate; try { - operation.End(); + operation.Join(); // Add the operations weight to the total amount of completed weight in the queue this.completedWeight += this.children[this.currentOperationIndex].Weight; @@ -141,7 +141,7 @@ namespace Nuclex.Support.Scheduling { // Execute the next operation unless we reached the end of the queue if(this.currentOperationIndex < this.children.Count) { - beginCurrentOperation(); + startCurrentOperation(); return; } diff --git a/Source/Scheduling/ThreadOperation.cs b/Source/Scheduling/ThreadOperation.cs index d61d41b..0adac8d 100644 --- a/Source/Scheduling/ThreadOperation.cs +++ b/Source/Scheduling/ThreadOperation.cs @@ -49,7 +49,7 @@ namespace Nuclex.Support.Scheduling { } /// Launches the background operation - public override void Begin() { + public override void Start() { if(useThreadPool) { ThreadPool.QueueUserWorkItem(callMethod); } else { diff --git a/Source/Tracking/FailableProgression.cs b/Source/Tracking/FailableProgression.cs new file mode 100644 index 0000000..720b818 --- /dev/null +++ b/Source/Tracking/FailableProgression.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; + +namespace Nuclex.Support.Tracking { + + /// Extended type of progression that is able to fail + /// + /// + /// If the background process fails, the exception that caused it to fail is + /// communicated to all parties waiting on the progression through the + /// Exception property. Implementers should place their code in try..catch + /// blocks and call SetException() to temporarily store the exception for + /// retrieval by the caller(s). + /// + /// + /// As with all progressions, the interface contract still requires you to call + /// OnAsyncEnded(), no matter what the outcome of your background operation is. + /// + /// + public class FailableProgression : Progression { + + #region class EndedDummyProgression + + /// Dummy progression that is always in the ended state + private class EndedDummyProgression : FailableProgression { + /// Creates a new successfully completed dummy progression + public EndedDummyProgression() : this(null) { } + /// Creates a new failed dummy progression + /// Exception that caused the dummy to fail + public EndedDummyProgression(Exception exception) { + OnAsyncEnded(); + + // Only call SetException() if an actual exception was provided. Who knows what + // evil code might be inside SetException() after all ;) + if(exception != null) + SetException(exception); + } + } + + #endregion // EndedDummyProgression + + /// Creates a new failed dummy progression + /// + /// Exception that supposedly caused the progression to fail + /// + /// + /// A failed progression that reports the provided exception as cause for its failure + /// + public static FailableProgression CreateFailedDummyProgression(Exception exception) { + return new EndedDummyProgression(exception); + } + + /// Waits for the background operation to end + /// + /// Any exceptions raised in the background operation will be thrown + /// in this method. If you decide to override this method, you should + /// call End() first (and let any possible exception through to your + /// caller). + /// + public virtual void Join() { + + // By design, end can only be called once! + lock(this) { + if(this.endCalled) + throw new InvalidOperationException("End() has already been called"); + + this.endCalled = true; + } + + // If the progression itself hasn't ended yet, block the caller until it has. + if(!Ended) + WaitHandle.WaitOne(); + + // If an exception occured during the background execution + if(this.occuredException != null) + throw this.occuredException; + + } + + /// Exception that occured while the operation was executing + /// + /// If this field is null, it is assumed that no exception has occured + /// in the background process. If it is set, however, the End() method will + /// re-raise the exception to the calling thread when it is called. + /// + public Exception OccuredException { + get { return this.occuredException; } + } + + /// Sets the exception to raise to the caller of the End() method + /// Exception to raise to the caller of the End() method + protected void SetException(Exception exception) { + + // We allow the caller to set the exception multiple times. While I certainly + // can't think of a scenario where this would happen, throwing an exception + // in that case seems worse. The caller might just be executing an exception + // handling block and locking + throwing here could cause all kinds of problems. + this.occuredException = exception; + + } + + /// Exception that occured while the operation was executing + private volatile Exception occuredException; + /// Whether the End() method has been called already + private volatile bool endCalled; + + } + +} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/IStatusReporter.cs b/Source/Tracking/IStatusReporter.cs index 6055051..1553fce 100644 --- a/Source/Tracking/IStatusReporter.cs +++ b/Source/Tracking/IStatusReporter.cs @@ -24,7 +24,7 @@ using System.Collections.Generic; namespace Nuclex.Support.Tracking { /// Interface for processes that report their status - interface IStatusReporter { + public interface IStatusReporter { /// Triggered when the status of the process changes event EventHandler StatusChanged;