diff --git a/Documents/Request Framework.txt b/Documents/Request Framework.txt new file mode 100644 index 0000000..5e9fd6f --- /dev/null +++ b/Documents/Request Framework.txt @@ -0,0 +1,68 @@ +The request framework should not require that .NET multithreading is used for +asynchronous requests. + +Otherwise, it would prvent overlapped operations, 3rd party APIs (eg. used +via P/Invoke) from being able to use the request framework and possibly even +spawn duplicate implementations. + + +Design using interfaces: + + interface IWaitable { + + /// Fired when the background process has finished + /// + /// If the process is already finished when a client registers to this event, + /// the registered callback will be invoked synchronously right when the + /// registration takes place. + /// + event EventHandler Finished; + + /// Waits until the background process finishes + void Wait(); + + /// 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 + /// + bool Wait(int timeoutMilliseconds); + + /// Whether the background process has finished + bool Finished { get; } + + } + + interface IThreadedWaitable : IWaitable { + + WaitHandle WaitHandle { get; } + + } + + interface IRequest : IWaitable { + + /// + /// Waits for the background process to complete and re-throws the exception to + /// the caller when an error has occured + /// + void Join(); + + } + + interface IRequest : IRequest { + + /// + /// Waits for the background process to complete and re-throws the exception to + /// the caller when an error has occured + /// + /// The result of the background processing + new ResultType Join(); + + } + + interface IThreadedRequest : IRequest, IThreadedWaitable { } + + interface IThreadedRequest : + IRequest, IThreadedRequest, IThreadedWaitable { } diff --git a/Nuclex.Support (x86).csproj b/Nuclex.Support (x86).csproj index 0a0e8ab..4a0ae00 100644 --- a/Nuclex.Support (x86).csproj +++ b/Nuclex.Support (x86).csproj @@ -217,6 +217,7 @@ + diff --git a/Source/Tracking/Request.cs b/Source/Tracking/Request.cs index 0477d94..27b9178 100644 --- a/Source/Tracking/Request.cs +++ b/Source/Tracking/Request.cs @@ -70,7 +70,7 @@ namespace Nuclex.Support.Tracking { /// /// A failed request that reports the provided exception as cause for its failure /// - public static Request CreateFailedDummyRequest(Exception exception) { + public static Request CreateFailedDummy(Exception exception) { return new EndedDummyRequest(exception); } @@ -134,7 +134,7 @@ namespace Nuclex.Support.Tracking { /// /// A failed request that reports the provided exception as cause for its failure /// - public static new Request CreateFailedDummyRequest(Exception exception) { + public static new Request CreateFailedDummy(Exception exception) { return new EndedDummyRequest(exception); } diff --git a/Source/Tracking/Waitable.cs b/Source/Tracking/Waitable.cs index 69237f4..65cd63b 100644 --- a/Source/Tracking/Waitable.cs +++ b/Source/Tracking/Waitable.cs @@ -23,9 +23,7 @@ using System.Threading; namespace Nuclex.Support.Tracking { - /// - /// Base class for actions on which that give an indication of their progress - /// + /// Base class for background processes the user can wait on /// /// /// By encapsulating long-running operations which will ideally be running in @@ -40,27 +38,27 @@ namespace Nuclex.Support.Tracking { /// task has completed. 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 being + /// possible to construct a Waitable just-in-time when it is explicitely being /// asked for. /// /// public abstract class Waitable { - #region class EndedDummyProgression + #region class EndedDummyWaitable - /// Dummy progression which always is in the 'ended' state + /// Dummy waitable which always is in the 'ended' state private class EndedDummyWaitable : Waitable { - /// Initializes a new ended dummy progression + /// Initializes a new ended dummy waitable public EndedDummyWaitable() { OnAsyncEnded(); } } - #endregion // class EndedDummyProgression + #endregion // class EndedDummyWaitable - /// A dummy progression that's always in the 'ended' state + /// A dummy waitable that's always in the 'ended' state /// /// 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 @@ -68,15 +66,15 @@ namespace Nuclex.Support.Tracking { /// public static readonly Waitable EndedDummy = new EndedDummyWaitable(); - /// Will be triggered when the progression has ended + /// Will be triggered when the Waitable has ended public event EventHandler AsyncEnded; - /// Whether the progression has ended already + /// Whether the Waitable has ended already public bool Ended { get { return this.ended; } } - /// WaitHandle that can be used to wait for the progression to end + /// WaitHandle that can be used to wait for the Waitable to end public WaitHandle WaitHandle { get { @@ -87,11 +85,11 @@ namespace Nuclex.Support.Tracking { // 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 referenced by this.doneEvent and thus gets set in the end. - if (this.doneEvent == null) { + if(this.doneEvent == null) { - lock (this) { + lock(this) { - if (this.doneEvent == null) + if(this.doneEvent == null) this.doneEvent = new ManualResetEvent(this.ended); } @@ -121,13 +119,13 @@ namespace Nuclex.Support.Tracking { // 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) { + 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) + if(this.ended) throw new InvalidOperationException("The progression has already been ended"); this.ended = true; @@ -137,12 +135,12 @@ namespace Nuclex.Support.Tracking { // 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) + if(this.doneEvent != null) this.doneEvent.Set(); // Finally, fire the AsyncEnded event EventHandler copy = AsyncEnded; - if (copy != null) + if(copy != null) copy(this, EventArgs.Empty); }