diff --git a/Documents/Request Framework.txt b/Documents/Request Framework.txt
index 6bf2df7..d8feaa0 100644
--- a/Documents/Request Framework.txt
+++ b/Documents/Request Framework.txt
@@ -33,6 +33,18 @@ Design using interfaces:
/// Whether the background process has finished
bool Finished { get; }
+// ?
+ /// Wait handle that can be used to wait for multiple waitables
+ ///
+ /// Only use the WaitHandle to wait if you're running in a different thread than
+ /// the request, or you may deadlock. Libraries based on single-threaded
+ /// multitasking may employ concepts such as window message processing to achieve
+ /// parallelism which could be stalled by improper waiting using the WaitHandle
+ /// whereas the Wait() method typically has a suitable implementation compatible
+ /// with the request's requirements.
+ ///
+ WaitHandle WaitHandle { get; }
+
}
interface IThreadedWaitable : IWaitable {
diff --git a/Source/Tracking/Request.cs b/Source/Tracking/Request.cs
index 27b9178..341bf9f 100644
--- a/Source/Tracking/Request.cs
+++ b/Source/Tracking/Request.cs
@@ -84,8 +84,11 @@ namespace Nuclex.Support.Tracking {
public virtual void Join() {
// If the progression itself hasn't ended yet, block the caller until it has.
+ // We could just use WaitHandle.WaitOne() here, but since the WaitHandle is created
+ // on-the-fly only when it is requested, we can avoid the WaitHandle creation in
+ // case the request is already finished!
if(!Ended)
- WaitHandle.WaitOne();
+ Wait();
// Allow the implementor to throw an exception in case an error has occured
ReraiseExceptions();
diff --git a/Source/Tracking/Waitable.cs b/Source/Tracking/Waitable.cs
index d5f7464..24bff27 100644
--- a/Source/Tracking/Waitable.cs
+++ b/Source/Tracking/Waitable.cs
@@ -73,7 +73,7 @@ namespace Nuclex.Support.Tracking {
/// the registered callback will be invoked synchronously right when the
/// registration takes place.
///
- public event EventHandler AsyncEnded {
+ public virtual event EventHandler AsyncEnded {
add {
// If the background process has not yet ended, add the delegate to the
@@ -84,12 +84,12 @@ namespace Nuclex.Support.Tracking {
if(!this.ended) {
// The subscriber list is also created lazily ;-)
- if(ReferenceEquals(this.subscribers, null)) {
- this.subscribers = new List();
+ if(ReferenceEquals(this.endedEventSubscribers, null)) {
+ this.endedEventSubscribers = new List();
}
// Subscribe the event handler to the list
- this.subscribers.Add(value);
+ this.endedEventSubscribers.Add(value);
return;
}
@@ -103,17 +103,17 @@ namespace Nuclex.Support.Tracking {
}
remove {
- // Unsubscribing a non-subscribed delegate from an event is allowed and should
- // not throw an exception. Due to the stupid design of the .NET collection
- // classes (has anyone at Microsoft ever written a single proper collection
- // in his life?) we have to search the collection twice.
lock(this) {
// Only try to remove the event handler if the subscriber list was created,
// otherwise, we can be sure that no actual subscribers exist.
- if(!ReferenceEquals(this.subscribers, null)) {
- if(this.subscribers.Contains(value)) {
- this.subscribers.Remove(value);
+ if(!ReferenceEquals(this.endedEventSubscribers, null)) {
+ int eventHandlerIndex = this.endedEventSubscribers.IndexOf(value);
+
+ // Unsubscribing a non-subscribed delegate from an event is allowed and should
+ // not throw an exception.
+ if(eventHandlerIndex != -1) {
+ this.endedEventSubscribers.RemoveAt(eventHandlerIndex);
}
}
@@ -122,13 +122,40 @@ namespace Nuclex.Support.Tracking {
}
}
+ /// Waits until the background process finishes
+ public virtual void Wait() {
+ WaitHandle.WaitOne();
+ }
+
+ /// Waits until the background process finishes or a timeout occurs
+ ///
+ /// Time span after which to stop waiting and return immediately
+ ///
+ ///
+ /// True if the background process completed, false if the timeout was reached
+ ///
+ public virtual bool Wait(TimeSpan timeout) {
+ return WaitHandle.WaitOne(timeout, false);
+ }
+
+ /// Waits until the background process finishes or a timeout occurs
+ ///
+ /// Number of milliseconds after which to stop waiting and return immediately
+ ///
+ ///
+ /// True if the background process completed, false if the timeout was reached
+ ///
+ public virtual bool Wait(int timeoutMilliseconds) {
+ return WaitHandle.WaitOne(timeoutMilliseconds, false);
+ }
+
/// Whether the Waitable has ended already
- public bool Ended {
+ public virtual bool Ended {
get { return this.ended; }
}
/// WaitHandle that can be used to wait for the Waitable to end
- public WaitHandle WaitHandle {
+ public virtual WaitHandle WaitHandle {
get {
// The WaitHandle will only be created when someone asks for it!
@@ -189,11 +216,15 @@ namespace Nuclex.Support.Tracking {
}
- // Fire the ended events to all event subscribers. We can freely use the list
- // without synchronization at this point on since once this.ended is set to true,
- // the subscribers list will not be accessed any longer
- for(int index = 0; index < this.subscribers.Count; ++index) {
- this.subscribers[index](this, EventArgs.Empty);
+ if(!ReferenceEquals(this.endedEventSubscribers, null)) {
+
+ // Fire the ended events to all event subscribers. We can freely use the list
+ // without synchronization at this point on since once this.ended is set to true,
+ // the subscribers list will not be accessed any longer
+ for(int index = 0; index < this.endedEventSubscribers.Count; ++index) {
+ this.endedEventSubscribers[index](this, EventArgs.Empty);
+ }
+
}
}
@@ -202,15 +233,15 @@ namespace Nuclex.Support.Tracking {
///
/// Does not need to be volatile since it's only accessed inside
///
- private volatile List subscribers;
+ protected volatile List endedEventSubscribers;
/// Whether the operation has completed yet
- private volatile bool ended;
+ protected volatile bool ended;
/// Event that will be set when the progression is completed
///
/// This event is will only be created when it is specifically asked for using
/// the WaitHandle property.
///
- private volatile ManualResetEvent doneEvent;
+ protected volatile ManualResetEvent doneEvent;
}