HistoryFunction.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. using PaintDotNet.Measurement.Enum;
  2. using System;
  3. using System.ComponentModel;
  4. using System.Threading;
  5. namespace PaintDotNet.Measurement
  6. {
  7. public abstract class HistoryFunction
  8. {
  9. private int criticalRegionCount = 0;
  10. private bool executed = false;
  11. private volatile bool pleaseCancel = false;
  12. private ISynchronizeInvoke eventSink = null;
  13. public bool IsAsync
  14. {
  15. get
  16. {
  17. return this.eventSink != null;
  18. }
  19. }
  20. public ISynchronizeInvoke EventSink
  21. {
  22. get
  23. {
  24. if (!IsAsync)
  25. {
  26. throw new InvalidOperationException("EventSink property is only accessible when IsAsync is true");
  27. }
  28. return this.eventSink;
  29. }
  30. }
  31. public bool Cancellable
  32. {
  33. get
  34. {
  35. return ((this.actionFlags & ActionFlags.Cancellable) == ActionFlags.Cancellable);
  36. }
  37. }
  38. private ActionFlags actionFlags;
  39. public ActionFlags ActionFlags
  40. {
  41. get
  42. {
  43. return this.actionFlags;
  44. }
  45. }
  46. protected bool PleaseCancel
  47. {
  48. get
  49. {
  50. return this.pleaseCancel;
  51. }
  52. }
  53. private void ExecuteTrampoline(object context)
  54. {
  55. Execute((IDocumentWorkspace)context);
  56. }
  57. /// <summary>
  58. /// Executes the HistoryFunction.
  59. /// </summary>
  60. /// <returns>
  61. /// A HistoryMemento instance if an operation was performed successfully,
  62. /// or null for success but no operation was performed.</returns>
  63. /// <exception cref="HistoryFunctionNonFatalException">
  64. /// There was error while performing the operation. No changes have been made to the HistoryWorkspace (no-op).
  65. /// </exception>
  66. /// <remarks>
  67. /// If this HistoryFunction's ActionFlags contain the HistoryFlags.Cancellable bit, then it will be executed in
  68. /// a background thread.
  69. /// </remarks>
  70. public HistoryMemento Execute(IDocumentWorkspace historyWorkspace)
  71. {
  72. SystemLayer.Tracing.LogFeature("HF(" + GetType().Name + ")");
  73. HistoryMemento returnVal = null;
  74. Exception exception = null;
  75. try
  76. {
  77. try
  78. {
  79. if (this.executed)
  80. {
  81. throw new InvalidOperationException("Already executed this HistoryFunction");
  82. }
  83. this.executed = true;
  84. returnVal = OnExecute(historyWorkspace);
  85. return returnVal;
  86. }
  87. catch (ArgumentOutOfRangeException aoorex)
  88. {
  89. if (this.criticalRegionCount > 0)
  90. {
  91. throw;
  92. }
  93. else
  94. {
  95. throw new HistoryFunctionNonFatalException(null, aoorex);
  96. }
  97. }
  98. catch (OutOfMemoryException oomex)
  99. {
  100. if (this.criticalRegionCount > 0)
  101. {
  102. throw;
  103. }
  104. else
  105. {
  106. throw new HistoryFunctionNonFatalException(null, oomex);
  107. }
  108. }
  109. }
  110. catch (Exception ex)
  111. {
  112. if (IsAsync)
  113. {
  114. exception = ex;
  115. return returnVal;
  116. }
  117. else
  118. {
  119. throw;
  120. }
  121. }
  122. finally
  123. {
  124. if (IsAsync)
  125. {
  126. OnFinished(returnVal, exception);
  127. }
  128. }
  129. }
  130. /// <summary>
  131. /// Executes the function asynchronously.
  132. /// </summary>
  133. /// <param name="eventSink"></param>
  134. /// <param name="historyWorkspace"></param>
  135. /// <remarks>
  136. /// If you use this method to execute the function, completion will be signified
  137. /// using the Finished event. This will be raised no matter if the function completes
  138. /// successfully or not.
  139. /// </remarks>
  140. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "eventSink")]
  141. public void BeginExecute(ISynchronizeInvoke eventSink, IDocumentWorkspace historyWorkspace, EventHandler<EventArgs<HistoryMemento>> finishedCallback)
  142. {
  143. if (finishedCallback == null)
  144. {
  145. throw new ArgumentNullException("finishedCallback");
  146. }
  147. if (this.eventSink != null)
  148. {
  149. throw new InvalidOperationException("already executing this function");
  150. }
  151. this.eventSink = eventSink;
  152. this.Finished += finishedCallback;
  153. System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(ExecuteTrampoline), historyWorkspace);
  154. }
  155. /// <summary>
  156. /// This event is raised when the function has finished execution, whether it finished successfully or not.
  157. /// </summary>
  158. public event EventHandler<EventArgs<HistoryMemento>> Finished;
  159. private void OnFinished(HistoryMemento memento, Exception exception)
  160. {
  161. if (this.eventSink.InvokeRequired)
  162. {
  163. this.eventSink.BeginInvoke(
  164. new Procedure<HistoryMemento, Exception>(OnFinished),
  165. new object[2] { memento, exception });
  166. }
  167. else
  168. {
  169. if (exception != null)
  170. {
  171. throw new WorkerThreadException(exception);
  172. }
  173. if (Finished != null)
  174. {
  175. Finished(this, new EventArgs<HistoryMemento>(memento));
  176. }
  177. }
  178. }
  179. /// <summary>
  180. /// This event is raised when the function wants to report its progress.
  181. /// </summary>
  182. /// <remarks>
  183. /// This event is only ever raised if the function has the ActionFlags.ReportsProgres flag set.
  184. /// There is no guarantee that the value reported via this event will start at 0 or finish at 100,
  185. /// nor that it will report values in non-descending order. Clients of this event are advised
  186. /// to clamp the values reported to the range [0, 100] and to define their own policy for
  187. /// handling progress values that are less than the previously reported progress values.
  188. /// </remarks>
  189. public event ProgressEventHandler Progress;
  190. protected virtual void OnProgress(double percent)
  191. {
  192. if ((this.actionFlags & ActionFlags.ReportsProgress) != ActionFlags.ReportsProgress)
  193. {
  194. System.Diagnostics.Debug.WriteLine("This HistoryFunction does not support reporting progress, yet it called OnProgress()");
  195. }
  196. else if (Progress != null)
  197. {
  198. this.eventSink.BeginInvoke(Progress, new object[2] { this, new ProgressEventArgs(percent) });
  199. }
  200. }
  201. /// <summary>
  202. /// Call this method from within OnExecute() in order to mark areas of your code where
  203. /// the throwing of an OutOfMemoryException does not guarantee the coherency of data.
  204. /// </summary>
  205. /// <remarks>
  206. /// If OnExecute() is not within a critical region and throws an OutOfMemoryException,
  207. /// it will be wrapped inside a HistoryFunctionNonFatalException as the InnerException.
  208. /// This prevents the execution host from treating it as an operation that must
  209. /// close the application.
  210. /// Once you enter a critical region, you may not leave it. Therefore, it is recommended
  211. /// that you do as much preparatory work as possible before entering a critical region.
  212. /// Once a HistoryFunction has entered its critical region, it may not be cancelled.
  213. /// </remarks>
  214. protected void EnterCriticalRegion()
  215. {
  216. Interlocked.Increment(ref this.criticalRegionCount);
  217. }
  218. /// <summary>
  219. /// If the HistoryFunction is being executed asynchronously using BeginExecute() and EndExecute(),
  220. /// and if it also has the ActionFlags.Cancellable flag, then you may request that it cancel its
  221. /// long running operation by calling this method.
  222. /// </summary>
  223. /// <remarks>
  224. /// The request to cancel may have no effect, and the history function may still complete normally.
  225. /// This may happen if the function has already entered its critical region, or if it has already
  226. /// completed before this method is called.
  227. /// </remarks>
  228. public void RequestCancel()
  229. {
  230. if (!Cancellable)
  231. {
  232. throw new InvalidOperationException("This HistoryFunction is not cancellable");
  233. }
  234. if (!IsAsync)
  235. {
  236. throw new InvalidOperationException("This function is not in the process of being executed asynchronously, and therefore cannot be cancelled");
  237. }
  238. this.pleaseCancel = true;
  239. OnCancelRequested();
  240. }
  241. public event EventHandler CancelRequested;
  242. protected virtual void OnCancelRequested()
  243. {
  244. if (!this.pleaseCancel)
  245. {
  246. throw new InvalidOperationException("OnCancelRequested() was called when pleaseCancel equaled false");
  247. }
  248. if (CancelRequested != null)
  249. {
  250. this.eventSink.BeginInvoke(CancelRequested, new object[2] { this, EventArgs.Empty });
  251. }
  252. }
  253. public abstract HistoryMemento OnExecute(IDocumentWorkspace historyWorkspace);
  254. public HistoryFunction(ActionFlags actionFlags)
  255. {
  256. this.actionFlags = actionFlags;
  257. }
  258. }
  259. }