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); } /// /// Executes the HistoryFunction. /// /// /// A HistoryMemento instance if an operation was performed successfully, /// or null for success but no operation was performed. /// /// There was error while performing the operation. No changes have been made to the HistoryWorkspace (no-op). /// /// /// If this HistoryFunction's ActionFlags contain the HistoryFlags.Cancellable bit, then it will be executed in /// a background thread. /// 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); } } } /// /// Executes the function asynchronously. /// /// /// /// /// 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. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "eventSink")] public void BeginExecute(ISynchronizeInvoke eventSink, IDocumentWorkspace historyWorkspace, EventHandler> 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); } /// /// This event is raised when the function has finished execution, whether it finished successfully or not. /// public event EventHandler> Finished; private void OnFinished(HistoryMemento memento, Exception exception) { if (this.eventSink.InvokeRequired) { this.eventSink.BeginInvoke( new Procedure(OnFinished), new object[2] { memento, exception }); } else { if (exception != null) { throw new WorkerThreadException(exception); } if (Finished != null) { Finished(this, new EventArgs(memento)); } } } /// /// This event is raised when the function wants to report its progress. /// /// /// 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. /// 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) }); } } /// /// 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. /// /// /// 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. /// protected void EnterCriticalRegion() { Interlocked.Increment(ref this.criticalRegionCount); } /// /// 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. /// /// /// 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. /// 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; } } }