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;