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; }