123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- using PaintDotNet.Measurement.Enum;
- using System;
- using System.ComponentModel;
- using System.Threading;
- namespace PaintDotNet.Measurement
- {
- public abstract class HistoryFunction
- {
- private int criticalRegionCount = 0;
- private bool executed = false;
- private volatile bool pleaseCancel = false;
- private ISynchronizeInvoke eventSink = null;
- public bool IsAsync
- {
- get
- {
- return this.eventSink != null;
- }
- }
- public ISynchronizeInvoke EventSink
- {
- get
- {
- if (!IsAsync)
- {
- throw new InvalidOperationException("EventSink property is only accessible when IsAsync is true");
- }
- return this.eventSink;
- }
- }
- public bool Cancellable
- {
- get
- {
- return ((this.actionFlags & ActionFlags.Cancellable) == ActionFlags.Cancellable);
- }
- }
- private ActionFlags actionFlags;
- public ActionFlags ActionFlags
- {
- get
- {
- return this.actionFlags;
- }
- }
- protected bool PleaseCancel
- {
- get
- {
- return this.pleaseCancel;
- }
- }
- private void ExecuteTrampoline(object context)
- {
- Execute((IDocumentWorkspace)context);
- }
- /// <summary>
- /// Executes the HistoryFunction.
- /// </summary>
- /// <returns>
- /// A HistoryMemento instance if an operation was performed successfully,
- /// or null for success but no operation was performed.</returns>
- /// <exception cref="HistoryFunctionNonFatalException">
- /// There was error while performing the operation. No changes have been made to the HistoryWorkspace (no-op).
- /// </exception>
- /// <remarks>
- /// If this HistoryFunction's ActionFlags contain the HistoryFlags.Cancellable bit, then it will be executed in
- /// a background thread.
- /// </remarks>
- public HistoryMemento Execute(IDocumentWorkspace historyWorkspace)
- {
- SystemLayer.Tracing.LogFeature("HF(" + GetType().Name + ")");
- HistoryMemento returnVal = null;
- Exception exception = null;
- try
- {
- try
- {
- if (this.executed)
- {
- throw new InvalidOperationException("Already executed this HistoryFunction");
- }
- this.executed = true;
- returnVal = OnExecute(historyWorkspace);
- return returnVal;
- }
- catch (ArgumentOutOfRangeException aoorex)
- {
- if (this.criticalRegionCount > 0)
- {
- throw;
- }
- else
- {
- throw new HistoryFunctionNonFatalException(null, aoorex);
- }
- }
- catch (OutOfMemoryException oomex)
- {
- if (this.criticalRegionCount > 0)
- {
- throw;
- }
- else
- {
- throw new HistoryFunctionNonFatalException(null, oomex);
- }
- }
- }
- catch (Exception ex)
- {
- if (IsAsync)
- {
- exception = ex;
- return returnVal;
- }
- else
- {
- throw;
- }
- }
- finally
- {
- if (IsAsync)
- {
- OnFinished(returnVal, exception);
- }
- }
- }
- /// <summary>
- /// Executes the function asynchronously.
- /// </summary>
- /// <param name="eventSink"></param>
- /// <param name="historyWorkspace"></param>
- /// <remarks>
- /// If you use this method to execute the function, completion will be signified
- /// using the Finished event. This will be raised no matter if the function completes
- /// successfully or not.
- /// </remarks>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "eventSink")]
- public void BeginExecute(ISynchronizeInvoke eventSink, IDocumentWorkspace historyWorkspace, EventHandler<EventArgs<HistoryMemento>> finishedCallback)
- {
- if (finishedCallback == null)
- {
- throw new ArgumentNullException("finishedCallback");
- }
- if (this.eventSink != null)
- {
- throw new InvalidOperationException("already executing this function");
- }
- this.eventSink = eventSink;
- this.Finished += finishedCallback;
- System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(ExecuteTrampoline), historyWorkspace);
- }
- /// <summary>
- /// This event is raised when the function has finished execution, whether it finished successfully or not.
- /// </summary>
- public event EventHandler<EventArgs<HistoryMemento>> Finished;
- private void OnFinished(HistoryMemento memento, Exception exception)
- {
- if (this.eventSink.InvokeRequired)
- {
- this.eventSink.BeginInvoke(
- new Procedure<HistoryMemento, Exception>(OnFinished),
- new object[2] { memento, exception });
- }
- else
- {
- if (exception != null)
- {
- throw new WorkerThreadException(exception);
- }
- if (Finished != null)
- {
- Finished(this, new EventArgs<HistoryMemento>(memento));
- }
- }
- }
- /// <summary>
- /// This event is raised when the function wants to report its progress.
- /// </summary>
- /// <remarks>
- /// This event is only ever raised if the function has the ActionFlags.ReportsProgres flag set.
- /// There is no guarantee that the value reported via this event will start at 0 or finish at 100,
- /// nor that it will report values in non-descending order. Clients of this event are advised
- /// to clamp the values reported to the range [0, 100] and to define their own policy for
- /// handling progress values that are less than the previously reported progress values.
- /// </remarks>
- public event ProgressEventHandler Progress;
- protected virtual void OnProgress(double percent)
- {
- if ((this.actionFlags & ActionFlags.ReportsProgress) != ActionFlags.ReportsProgress)
- {
- System.Diagnostics.Debug.WriteLine("This HistoryFunction does not support reporting progress, yet it called OnProgress()");
- }
- else if (Progress != null)
- {
- this.eventSink.BeginInvoke(Progress, new object[2] { this, new ProgressEventArgs(percent) });
- }
- }
- /// <summary>
- /// Call this method from within OnExecute() in order to mark areas of your code where
- /// the throwing of an OutOfMemoryException does not guarantee the coherency of data.
- /// </summary>
- /// <remarks>
- /// If OnExecute() is not within a critical region and throws an OutOfMemoryException,
- /// it will be wrapped inside a HistoryFunctionNonFatalException as the InnerException.
- /// This prevents the execution host from treating it as an operation that must
- /// close the application.
- /// Once you enter a critical region, you may not leave it. Therefore, it is recommended
- /// that you do as much preparatory work as possible before entering a critical region.
- /// Once a HistoryFunction has entered its critical region, it may not be cancelled.
- /// </remarks>
- protected void EnterCriticalRegion()
- {
- Interlocked.Increment(ref this.criticalRegionCount);
- }
- /// <summary>
- /// If the HistoryFunction is being executed asynchronously using BeginExecute() and EndExecute(),
- /// and if it also has the ActionFlags.Cancellable flag, then you may request that it cancel its
- /// long running operation by calling this method.
- /// </summary>
- /// <remarks>
- /// The request to cancel may have no effect, and the history function may still complete normally.
- /// This may happen if the function has already entered its critical region, or if it has already
- /// completed before this method is called.
- /// </remarks>
- public void RequestCancel()
- {
- if (!Cancellable)
- {
- throw new InvalidOperationException("This HistoryFunction is not cancellable");
- }
- if (!IsAsync)
- {
- throw new InvalidOperationException("This function is not in the process of being executed asynchronously, and therefore cannot be cancelled");
- }
- this.pleaseCancel = true;
- OnCancelRequested();
- }
- public event EventHandler CancelRequested;
- protected virtual void OnCancelRequested()
- {
- if (!this.pleaseCancel)
- {
- throw new InvalidOperationException("OnCancelRequested() was called when pleaseCancel equaled false");
- }
- if (CancelRequested != null)
- {
- this.eventSink.BeginInvoke(CancelRequested, new object[2] { this, EventArgs.Empty });
- }
- }
- public abstract HistoryMemento OnExecute(IDocumentWorkspace historyWorkspace);
- public HistoryFunction(ActionFlags actionFlags)
- {
- this.actionFlags = actionFlags;
- }
- }
- }
|