WorkItem.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974
  1. using System;
  2. using System.Diagnostics;
  3. using System.Threading;
  4. namespace Amib.Threading.Internal
  5. {
  6. /// <summary>
  7. /// Holds a callback delegate and the state for that delegate.
  8. /// </summary>
  9. public partial class WorkItem
  10. {
  11. #region WorkItemState enum
  12. /// <summary>
  13. /// Indicates the state of the work item in the thread pool
  14. /// </summary>
  15. private enum WorkItemState
  16. {
  17. InQueue = 0, // Nexts: InProgress, Canceled
  18. InProgress = 1, // Nexts: Completed, Canceled
  19. Completed = 2, // Stays Completed
  20. Canceled = 3, // Stays Canceled
  21. }
  22. private static bool IsValidStatesTransition(WorkItemState currentState, WorkItemState nextState)
  23. {
  24. bool valid = false;
  25. switch (currentState)
  26. {
  27. case WorkItemState.InQueue:
  28. valid = (WorkItemState.InProgress == nextState) || (WorkItemState.Canceled == nextState);
  29. break;
  30. case WorkItemState.InProgress:
  31. valid = (WorkItemState.Completed == nextState) || (WorkItemState.Canceled == nextState);
  32. break;
  33. case WorkItemState.Completed:
  34. case WorkItemState.Canceled:
  35. // Cannot be changed
  36. break;
  37. default:
  38. // Unknown state
  39. Debug.Assert(false);
  40. break;
  41. }
  42. return valid;
  43. }
  44. #endregion
  45. #region Fields
  46. /// <summary>
  47. /// Callback delegate for the callback.
  48. /// </summary>
  49. private WorkItemCallback m_callback;
  50. private WaitCallback m_callbackNoResult;
  51. /// <summary>
  52. /// State with which to call the callback delegate.
  53. /// </summary>
  54. private object m_state;
  55. /// <summary>
  56. /// Stores the caller's context
  57. /// </summary>
  58. private ExecutionContext m_callerContext = null;
  59. /// <summary>
  60. /// Holds the result of the mehtod
  61. /// </summary>
  62. private object m_result;
  63. /// <summary>
  64. /// Hold the exception if the method threw it
  65. /// </summary>
  66. private Exception m_exception;
  67. /// <summary>
  68. /// Hold the state of the work item
  69. /// </summary>
  70. private WorkItemState m_workItemState;
  71. /// <summary>
  72. /// A ManualResetEvent to indicate that the result is ready
  73. /// </summary>
  74. private ManualResetEvent m_workItemCompleted;
  75. /// <summary>
  76. /// A reference count to the _workItemCompleted.
  77. /// When it reaches to zero _workItemCompleted is Closed
  78. /// </summary>
  79. private int m_workItemCompletedRefCount;
  80. /// <summary>
  81. /// Represents the result state of the work item
  82. /// </summary>
  83. private readonly WorkItemResult m_workItemResult;
  84. /// <summary>
  85. /// Work item info
  86. /// </summary>
  87. private readonly WorkItemInfo m_workItemInfo;
  88. /// <summary>
  89. /// Called when the WorkItem starts
  90. /// </summary>
  91. private event WorkItemStateCallback m_workItemStartedEvent;
  92. /// <summary>
  93. /// Called when the WorkItem completes
  94. /// </summary>
  95. private event WorkItemStateCallback m_workItemCompletedEvent;
  96. /// <summary>
  97. /// A reference to an object that indicates whatever the
  98. /// WorkItemsGroup has been canceled
  99. /// </summary>
  100. private CanceledWorkItemsGroup m_canceledWorkItemsGroup = CanceledWorkItemsGroup.NotCanceledWorkItemsGroup;
  101. /// <summary>
  102. /// A reference to an object that indicates whatever the
  103. /// SmartThreadPool has been canceled
  104. /// </summary>
  105. private CanceledWorkItemsGroup m_canceledSmartThreadPool = CanceledWorkItemsGroup.NotCanceledWorkItemsGroup;
  106. /// <summary>
  107. /// The work item group this work item belong to.
  108. /// </summary>
  109. private readonly IWorkItemsGroup m_workItemsGroup;
  110. /// <summary>
  111. /// The thread that executes this workitem.
  112. /// This field is available for the period when the work item is executed, before and after it is null.
  113. /// </summary>
  114. private Thread m_executingThread;
  115. /// <summary>
  116. /// The absulote time when the work item will be timeout
  117. /// </summary>
  118. private long m_expirationTime;
  119. #region Performance Counter fields
  120. /// <summary>
  121. /// Stores how long the work item waited on the stp queue
  122. /// </summary>
  123. private Stopwatch _waitingOnQueueStopwatch;
  124. /// <summary>
  125. /// Stores how much time it took the work item to execute after it went out of the queue
  126. /// </summary>
  127. private Stopwatch _processingStopwatch;
  128. #endregion
  129. #endregion
  130. #region Properties
  131. public TimeSpan WaitingTime
  132. {
  133. get
  134. {
  135. return _waitingOnQueueStopwatch.Elapsed;
  136. }
  137. }
  138. public TimeSpan ProcessTime
  139. {
  140. get
  141. {
  142. return _processingStopwatch.Elapsed;
  143. }
  144. }
  145. internal WorkItemInfo WorkItemInfo
  146. {
  147. get
  148. {
  149. return m_workItemInfo;
  150. }
  151. }
  152. #endregion
  153. #region Construction
  154. /// <summary>
  155. /// Initialize the callback holding object.
  156. /// </summary>
  157. /// <param name="workItemsGroup">The workItemGroup of the workitem</param>
  158. /// <param name="workItemInfo">The WorkItemInfo of te workitem</param>
  159. /// <param name="callback">Callback delegate for the callback.</param>
  160. /// <param name="state">State with which to call the callback delegate.</param>
  161. ///
  162. /// We assume that the WorkItem object is created within the thread
  163. /// that meant to run the callback
  164. public WorkItem(IWorkItemsGroup workItemsGroup, WorkItemInfo workItemInfo, WorkItemCallback callback, object state)
  165. {
  166. m_workItemsGroup = workItemsGroup;
  167. m_workItemInfo = workItemInfo;
  168. if (m_workItemInfo.UseCallerCallContext && !ExecutionContext.IsFlowSuppressed())
  169. {
  170. ExecutionContext ec = ExecutionContext.Capture();
  171. if (ec is not null)
  172. {
  173. m_callerContext = ec.CreateCopy();
  174. ec.Dispose();
  175. ec = null;
  176. }
  177. }
  178. m_callback = callback;
  179. m_callbackNoResult = null;
  180. m_state = state;
  181. m_workItemResult = new WorkItemResult(this);
  182. Initialize();
  183. }
  184. public WorkItem(IWorkItemsGroup workItemsGroup, WorkItemInfo workItemInfo, WaitCallback callback, object state)
  185. {
  186. m_workItemsGroup = workItemsGroup;
  187. m_workItemInfo = workItemInfo;
  188. if (m_workItemInfo.UseCallerCallContext && !ExecutionContext.IsFlowSuppressed())
  189. {
  190. ExecutionContext ec = ExecutionContext.Capture();
  191. if (ec is not null)
  192. {
  193. m_callerContext = ec.CreateCopy();
  194. ec.Dispose();
  195. ec = null;
  196. }
  197. }
  198. m_callbackNoResult = callback;
  199. m_state = state;
  200. m_workItemResult = new WorkItemResult(this);
  201. Initialize();
  202. }
  203. internal void Initialize()
  204. {
  205. // The _workItemState is changed directly instead of using the SetWorkItemState
  206. // method since we don't want to go throught IsValidStateTransition.
  207. m_workItemState = WorkItemState.InQueue;
  208. m_workItemCompleted = null;
  209. m_workItemCompletedRefCount = 0;
  210. _waitingOnQueueStopwatch = new Stopwatch();
  211. _processingStopwatch = new Stopwatch();
  212. m_expirationTime = m_workItemInfo.Timeout > 0 ? DateTime.UtcNow.Ticks + m_workItemInfo.Timeout * TimeSpan.TicksPerMillisecond : long.MaxValue;
  213. }
  214. internal bool WasQueuedBy(IWorkItemsGroup workItemsGroup)
  215. {
  216. return (workItemsGroup == m_workItemsGroup);
  217. }
  218. #endregion
  219. #region Methods
  220. internal CanceledWorkItemsGroup CanceledWorkItemsGroup
  221. {
  222. get { return m_canceledWorkItemsGroup; }
  223. set { m_canceledWorkItemsGroup = value; }
  224. }
  225. internal CanceledWorkItemsGroup CanceledSmartThreadPool
  226. {
  227. get { return m_canceledSmartThreadPool; }
  228. set { m_canceledSmartThreadPool = value; }
  229. }
  230. /// <summary>
  231. /// Change the state of the work item to in progress if it wasn't canceled.
  232. /// </summary>
  233. /// <returns>
  234. /// Return true on success or false in case the work item was canceled.
  235. /// If the work item needs to run a post execute then the method will return true.
  236. /// </returns>
  237. public bool StartingWorkItem()
  238. {
  239. _waitingOnQueueStopwatch.Stop();
  240. _processingStopwatch.Start();
  241. lock (this)
  242. {
  243. if (IsCanceled)
  244. {
  245. if ((m_workItemInfo.PostExecuteWorkItemCallback is not null) &&
  246. ((m_workItemInfo.CallToPostExecute & CallToPostExecute.WhenWorkItemCanceled) == CallToPostExecute.WhenWorkItemCanceled))
  247. {
  248. return true;
  249. }
  250. return false;
  251. }
  252. Debug.Assert(WorkItemState.InQueue == GetWorkItemState());
  253. // No need for a lock yet, only after the state has changed to InProgress
  254. m_executingThread = Thread.CurrentThread;
  255. SetWorkItemState(WorkItemState.InProgress);
  256. }
  257. return true;
  258. }
  259. /// <summary>
  260. /// Execute the work item and the post execute
  261. /// </summary>
  262. public void Execute()
  263. {
  264. CallToPostExecute currentCallToPostExecute = 0;
  265. // Execute the work item if we are in the correct state
  266. switch (GetWorkItemState())
  267. {
  268. case WorkItemState.InProgress:
  269. currentCallToPostExecute |= CallToPostExecute.WhenWorkItemNotCanceled;
  270. ExecuteWorkItem();
  271. break;
  272. case WorkItemState.Canceled:
  273. currentCallToPostExecute |= CallToPostExecute.WhenWorkItemCanceled;
  274. break;
  275. default:
  276. Debug.Assert(false);
  277. throw new NotSupportedException();
  278. }
  279. // Run the post execute as needed
  280. if ((currentCallToPostExecute & m_workItemInfo.CallToPostExecute) != 0)
  281. {
  282. PostExecute();
  283. }
  284. _processingStopwatch.Stop();
  285. }
  286. internal void FireWorkItemCompleted()
  287. {
  288. try
  289. {
  290. m_workItemCompletedEvent?.Invoke(this);
  291. }
  292. catch // Suppress exceptions
  293. { }
  294. }
  295. internal void FireWorkItemStarted()
  296. {
  297. try
  298. {
  299. m_workItemStartedEvent?.Invoke(this);
  300. }
  301. catch // Suppress exceptions
  302. { }
  303. }
  304. /// <summary>
  305. /// Execute the work item
  306. /// </summary>
  307. private void ExecuteWorkItem()
  308. {
  309. Exception exception = null;
  310. object result = null;
  311. try
  312. {
  313. try
  314. {
  315. if(m_callbackNoResult is null)
  316. {
  317. if(m_callerContext is null)
  318. result = m_callback(m_state);
  319. else
  320. {
  321. ContextCallback _ccb = new( o => { result =m_callback(o); });
  322. ExecutionContext.Run(m_callerContext, _ccb, m_state);
  323. }
  324. }
  325. else
  326. {
  327. if (m_callerContext is null)
  328. m_callbackNoResult(m_state);
  329. else
  330. {
  331. ContextCallback _ccb = new(o => { m_callbackNoResult(o); });
  332. ExecutionContext.Run(m_callerContext, _ccb, m_state);
  333. }
  334. }
  335. }
  336. catch (Exception e)
  337. {
  338. // Save the exception so we can rethrow it later
  339. exception = e;
  340. }
  341. // Remove the value of the execution thread, so it will be impossible to cancel the work item,
  342. // since it is already completed.
  343. // Cancelling a work item that already completed may cause the abortion of the next work item!!!
  344. Thread executionThread = Interlocked.CompareExchange(ref m_executingThread, null, m_executingThread);
  345. if (executionThread is null)
  346. {
  347. // Oops! we are going to be aborted..., Wait here so we can catch the ThreadAbortException
  348. Thread.Sleep(60 * 1000);
  349. // If after 1 minute this thread was not aborted then let it continue working.
  350. }
  351. }
  352. // We must treat the ThreadAbortException or else it will be stored in the exception variable
  353. catch (ThreadAbortException tae)
  354. {
  355. // Check if the work item was cancelled
  356. // If we got a ThreadAbortException and the STP is not shutting down, it means the
  357. // work items was cancelled.
  358. tae.GetHashCode();
  359. //if (!SmartThreadPool.CurrentThreadEntry.AssociatedSmartThreadPool.IsShuttingdown)
  360. //{
  361. // Thread.ResetAbort();
  362. //}
  363. }
  364. if (!SmartThreadPool.IsWorkItemCanceled)
  365. {
  366. SetResult(result, exception);
  367. }
  368. }
  369. /// <summary>
  370. /// Runs the post execute callback
  371. /// </summary>
  372. private void PostExecute()
  373. {
  374. if (m_workItemInfo.PostExecuteWorkItemCallback is not null)
  375. {
  376. try
  377. {
  378. m_workItemInfo.PostExecuteWorkItemCallback(m_workItemResult);
  379. }
  380. catch (Exception e)
  381. {
  382. Debug.Assert(e is not null);
  383. }
  384. }
  385. }
  386. /// <summary>
  387. /// Set the result of the work item to return
  388. /// </summary>
  389. /// <param name="result">The result of the work item</param>
  390. /// <param name="exception">The exception that was throw while the workitem executed, null
  391. /// if there was no exception.</param>
  392. internal void SetResult(object result, Exception exception)
  393. {
  394. m_result = result;
  395. m_exception = exception;
  396. SignalComplete(false);
  397. }
  398. /// <summary>
  399. /// Returns the work item result
  400. /// </summary>
  401. /// <returns>The work item result</returns>
  402. internal IWorkItemResult GetWorkItemResult()
  403. {
  404. return m_workItemResult;
  405. }
  406. /// <summary>
  407. /// Wait for all work items to complete
  408. /// </summary>
  409. /// <param name="waitableResults">Array of work item result objects</param>
  410. /// <param name="millisecondsTimeout">The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely.</param>
  411. /// <param name="exitContext">
  412. /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false.
  413. /// </param>
  414. /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the wait if needed</param>
  415. /// <returns>
  416. /// true when every work item in waitableResults has completed; otherwise false.
  417. /// </returns>
  418. internal static bool WaitAll( IWaitableResult[] waitableResults, int millisecondsTimeout, bool exitContext,
  419. WaitHandle cancelWaitHandle)
  420. {
  421. if (0 == waitableResults.Length)
  422. {
  423. return true;
  424. }
  425. bool success;
  426. WaitHandle[] waitHandles = new WaitHandle[waitableResults.Length];
  427. GetWaitHandles(waitableResults, waitHandles);
  428. if ((cancelWaitHandle is null) && (waitHandles.Length <= 64))
  429. {
  430. success = STPEventWaitHandle.WaitAll(waitHandles, millisecondsTimeout, exitContext);
  431. }
  432. else
  433. {
  434. success = true;
  435. int millisecondsLeft = millisecondsTimeout;
  436. Stopwatch stopwatch = Stopwatch.StartNew();
  437. WaitHandle[] whs = cancelWaitHandle is null ?
  438. new WaitHandle[] { null } :
  439. new WaitHandle[] { null, cancelWaitHandle };
  440. bool waitInfinitely = (Timeout.Infinite == millisecondsTimeout);
  441. // Iterate over the wait handles and wait for each one to complete.
  442. // We cannot use WaitHandle.WaitAll directly, because the cancelWaitHandle
  443. // won't affect it.
  444. // Each iteration we update the time left for the timeout.
  445. for (int i = 0; i < waitableResults.Length; ++i)
  446. {
  447. // WaitAny don't work with negative numbers
  448. if (!waitInfinitely && (millisecondsLeft < 0))
  449. {
  450. success = false;
  451. break;
  452. }
  453. whs[0] = waitHandles[i];
  454. int result = STPEventWaitHandle.WaitAny(whs, millisecondsLeft, exitContext);
  455. if ((result > 0) || (STPEventWaitHandle.WaitTimeout == result))
  456. {
  457. success = false;
  458. break;
  459. }
  460. if (!waitInfinitely)
  461. {
  462. // Update the time left to wait
  463. millisecondsLeft = millisecondsTimeout - (int)stopwatch.ElapsedMilliseconds;
  464. }
  465. }
  466. }
  467. // Release the wait handles
  468. ReleaseWaitHandles(waitableResults);
  469. return success;
  470. }
  471. /// <summary>
  472. /// Waits for any of the work items in the specified array to complete, cancel, or timeout
  473. /// </summary>
  474. /// <param name="waitableResults">Array of work item result objects</param>
  475. /// <param name="millisecondsTimeout">The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely.</param>
  476. /// <param name="exitContext">
  477. /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false.
  478. /// </param>
  479. /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the wait if needed</param>
  480. /// <returns>
  481. /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled.
  482. /// </returns>
  483. internal static int WaitAny( IWaitableResult[] waitableResults, int millisecondsTimeout,
  484. bool exitContext, WaitHandle cancelWaitHandle)
  485. {
  486. WaitHandle[] waitHandles;
  487. if (cancelWaitHandle is not null)
  488. {
  489. waitHandles = new WaitHandle[waitableResults.Length + 1];
  490. GetWaitHandles(waitableResults, waitHandles);
  491. waitHandles[waitableResults.Length] = cancelWaitHandle;
  492. }
  493. else
  494. {
  495. waitHandles = new WaitHandle[waitableResults.Length];
  496. GetWaitHandles(waitableResults, waitHandles);
  497. }
  498. int result = STPEventWaitHandle.WaitAny(waitHandles, millisecondsTimeout, exitContext);
  499. // Treat cancel as timeout
  500. if (cancelWaitHandle is not null)
  501. {
  502. if (result == waitableResults.Length)
  503. {
  504. result = STPEventWaitHandle.WaitTimeout;
  505. }
  506. }
  507. ReleaseWaitHandles(waitableResults);
  508. return result;
  509. }
  510. /// <summary>
  511. /// Fill an array of wait handles with the work items wait handles.
  512. /// </summary>
  513. /// <param name="waitableResults">An array of work item results</param>
  514. /// <param name="waitHandles">An array of wait handles to fill</param>
  515. private static void GetWaitHandles(IWaitableResult[] waitableResults,
  516. WaitHandle[] waitHandles)
  517. {
  518. for (int i = 0; i < waitableResults.Length; ++i)
  519. {
  520. WorkItemResult wir = waitableResults[i].GetWorkItemResult() as WorkItemResult;
  521. Debug.Assert(wir is not null, "All waitableResults must be WorkItemResult objects");
  522. waitHandles[i] = wir.GetWorkItem().GetWaitHandle();
  523. }
  524. }
  525. /// <summary>
  526. /// Release the work items' wait handles
  527. /// </summary>
  528. /// <param name="waitableResults">An array of work item results</param>
  529. private static void ReleaseWaitHandles(IWaitableResult[] waitableResults)
  530. {
  531. for (int i = 0; i < waitableResults.Length; ++i)
  532. {
  533. WorkItemResult wir = (WorkItemResult)waitableResults[i].GetWorkItemResult();
  534. wir.GetWorkItem().ReleaseWaitHandle();
  535. }
  536. }
  537. #endregion
  538. #region Private Members
  539. private WorkItemState GetWorkItemState()
  540. {
  541. lock (this)
  542. {
  543. if (WorkItemState.Completed == m_workItemState)
  544. {
  545. return m_workItemState;
  546. }
  547. if (WorkItemState.Canceled != m_workItemState && DateTime.UtcNow.Ticks > m_expirationTime)
  548. {
  549. m_workItemState = WorkItemState.Canceled;
  550. return m_workItemState;
  551. }
  552. if(WorkItemState.InProgress != m_workItemState)
  553. {
  554. if (CanceledSmartThreadPool.IsCanceled || CanceledWorkItemsGroup.IsCanceled)
  555. {
  556. return WorkItemState.Canceled;
  557. }
  558. }
  559. return m_workItemState;
  560. }
  561. }
  562. /// <summary>
  563. /// Sets the work item's state
  564. /// </summary>
  565. /// <param name="workItemState">The state to set the work item to</param>
  566. private void SetWorkItemState(WorkItemState workItemState)
  567. {
  568. lock (this)
  569. {
  570. if (IsValidStatesTransition(m_workItemState, workItemState))
  571. {
  572. m_workItemState = workItemState;
  573. }
  574. }
  575. }
  576. /// <summary>
  577. /// Signals that work item has been completed or canceled
  578. /// </summary>
  579. /// <param name="canceled">Indicates that the work item has been canceled</param>
  580. private void SignalComplete(bool canceled)
  581. {
  582. SetWorkItemState(canceled ? WorkItemState.Canceled : WorkItemState.Completed);
  583. lock (this)
  584. {
  585. // If someone is waiting then signal.
  586. m_workItemCompleted?.Set();
  587. }
  588. }
  589. internal void WorkItemIsQueued()
  590. {
  591. _waitingOnQueueStopwatch.Start();
  592. }
  593. #endregion
  594. #region Members exposed by WorkItemResult
  595. /// <summary>
  596. /// Cancel the work item if it didn't start running yet.
  597. /// </summary>
  598. /// <returns>Returns true on success or false if the work item is in progress or already completed</returns>
  599. private bool Cancel(bool abortExecution)
  600. {
  601. bool success = false;
  602. bool signalComplete = false;
  603. lock (this)
  604. {
  605. switch (GetWorkItemState())
  606. {
  607. case WorkItemState.Canceled:
  608. //Debug.WriteLine("Work item already canceled");
  609. if (abortExecution)
  610. {
  611. Thread executionThread = Interlocked.CompareExchange(ref m_executingThread, null, m_executingThread);
  612. if (executionThread is not null)
  613. {
  614. //executionThread.Abort(); // "Cancel"
  615. // No need to signalComplete, because we already cancelled this work item
  616. // so it already signaled its completion.
  617. //signalComplete = true;
  618. }
  619. }
  620. success = true;
  621. break;
  622. case WorkItemState.Completed:
  623. //Debug.WriteLine("Work item cannot be canceled");
  624. break;
  625. case WorkItemState.InProgress:
  626. if (abortExecution)
  627. {
  628. Thread executionThread = Interlocked.CompareExchange(ref m_executingThread, null, m_executingThread);
  629. if (executionThread is not null)
  630. {
  631. //executionThread.Abort(); // "Cancel"
  632. success = true;
  633. signalComplete = true;
  634. }
  635. }
  636. else
  637. {
  638. // **************************
  639. // Stock SmartThreadPool 2.2.3 sets these to true and relies on the thread to check the
  640. // WorkItem cancellation status. However, OpenSimulator uses a different mechanism to notify
  641. // scripts of co-operative termination and the abort code also relies on this method
  642. // returning false in order to implement a small wait.
  643. //
  644. // Therefore, as was the case previously with STP, we will not signal successful cancellation
  645. // here. It's possible that OpenSimulator code could be changed in the future to remove
  646. // the need for this change.
  647. // **************************
  648. success = false;
  649. signalComplete = false;
  650. }
  651. break;
  652. case WorkItemState.InQueue:
  653. // Signal to the wait for completion that the work
  654. // item has been completed (canceled). There is no
  655. // reason to wait for it to get out of the queue
  656. signalComplete = true;
  657. //Debug.WriteLine("Work item canceled");
  658. success = true;
  659. break;
  660. }
  661. if (signalComplete)
  662. {
  663. SignalComplete(true);
  664. }
  665. }
  666. return success;
  667. }
  668. /// <summary>
  669. /// Get the result of the work item.
  670. /// If the work item didn't run yet then the caller waits for the result, timeout, or cancel.
  671. /// In case of error the method throws and exception
  672. /// </summary>
  673. /// <returns>The result of the work item</returns>
  674. private object GetResult(int millisecondsTimeout, bool exitContext,
  675. WaitHandle cancelWaitHandle)
  676. {
  677. object result = GetResult(millisecondsTimeout, exitContext, cancelWaitHandle, out Exception e);
  678. if (e is not null)
  679. {
  680. throw new WorkItemResultException("The work item caused an excpetion, see the inner exception for details", e);
  681. }
  682. return result;
  683. }
  684. /// <summary>
  685. /// Get the result of the work item.
  686. /// If the work item didn't run yet then the caller waits for the result, timeout, or cancel.
  687. /// In case of error the e argument is filled with the exception
  688. /// </summary>
  689. /// <returns>The result of the work item</returns>
  690. private object GetResult( int millisecondsTimeout, bool exitContext,
  691. WaitHandle cancelWaitHandle, out Exception e)
  692. {
  693. e = null;
  694. // Check for cancel
  695. if (WorkItemState.Canceled == GetWorkItemState())
  696. {
  697. throw new WorkItemCancelException("Work item canceled");
  698. }
  699. // Check for completion
  700. if (IsCompleted)
  701. {
  702. e = m_exception;
  703. return m_result;
  704. }
  705. // If no cancelWaitHandle is provided
  706. if (cancelWaitHandle is null)
  707. {
  708. WaitHandle wh = GetWaitHandle();
  709. bool timeout = !STPEventWaitHandle.WaitOne(wh, millisecondsTimeout, exitContext);
  710. ReleaseWaitHandle();
  711. if (timeout)
  712. {
  713. throw new WorkItemTimeoutException("Work item timeout");
  714. }
  715. }
  716. else
  717. {
  718. WaitHandle wh = GetWaitHandle();
  719. int result = STPEventWaitHandle.WaitAny(new WaitHandle[] { wh, cancelWaitHandle });
  720. ReleaseWaitHandle();
  721. switch (result)
  722. {
  723. case 0:
  724. // The work item signaled
  725. // Note that the signal could be also as a result of canceling the
  726. // work item (not the get result)
  727. break;
  728. case 1:
  729. case STPEventWaitHandle.WaitTimeout:
  730. throw new WorkItemTimeoutException("Work item timeout");
  731. default:
  732. Debug.Assert(false);
  733. break;
  734. }
  735. }
  736. // Check for cancel
  737. if (WorkItemState.Canceled == GetWorkItemState())
  738. {
  739. throw new WorkItemCancelException("Work item canceled");
  740. }
  741. Debug.Assert(IsCompleted);
  742. e = m_exception;
  743. // Return the result
  744. return m_result;
  745. }
  746. /// <summary>
  747. /// A wait handle to wait for completion, cancel, or timeout
  748. /// </summary>
  749. private WaitHandle GetWaitHandle()
  750. {
  751. lock (this)
  752. {
  753. if (m_workItemCompleted is null)
  754. {
  755. m_workItemCompleted = new ManualResetEvent(IsCompleted);
  756. }
  757. ++m_workItemCompletedRefCount;
  758. }
  759. return m_workItemCompleted;
  760. }
  761. private void ReleaseWaitHandle()
  762. {
  763. lock (this)
  764. {
  765. if (m_workItemCompleted is not null)
  766. {
  767. --m_workItemCompletedRefCount;
  768. if (0 == m_workItemCompletedRefCount)
  769. {
  770. m_workItemCompleted.Close();
  771. m_workItemCompleted = null;
  772. }
  773. }
  774. }
  775. }
  776. /// <summary>
  777. /// Returns true when the work item has completed or canceled
  778. /// </summary>
  779. private bool IsCompleted
  780. {
  781. get
  782. {
  783. lock (this)
  784. {
  785. WorkItemState workItemState = GetWorkItemState();
  786. return ((workItemState == WorkItemState.Completed) ||
  787. (workItemState == WorkItemState.Canceled));
  788. }
  789. }
  790. }
  791. /// <summary>
  792. /// Returns true when the work item has canceled
  793. /// </summary>
  794. public bool IsCanceled
  795. {
  796. get
  797. {
  798. lock (this)
  799. {
  800. return (GetWorkItemState() == WorkItemState.Canceled);
  801. }
  802. }
  803. }
  804. #endregion
  805. internal event WorkItemStateCallback OnWorkItemStarted
  806. {
  807. add
  808. {
  809. m_workItemStartedEvent += value;
  810. }
  811. remove
  812. {
  813. m_workItemStartedEvent -= value;
  814. }
  815. }
  816. internal event WorkItemStateCallback OnWorkItemCompleted
  817. {
  818. add
  819. {
  820. m_workItemCompletedEvent += value;
  821. }
  822. remove
  823. {
  824. m_workItemCompletedEvent -= value;
  825. }
  826. }
  827. public void DisposeOfState()
  828. {
  829. if(m_callerContext is not null)
  830. {
  831. m_callerContext.Dispose();
  832. m_callerContext = null;
  833. }
  834. if(m_workItemCompleted is not null)
  835. {
  836. m_workItemCompleted.Dispose();
  837. m_workItemCompleted = null;
  838. }
  839. if (m_workItemInfo.DisposeOfStateObjects)
  840. {
  841. if (m_state is IDisposable disp)
  842. {
  843. disp.Dispose();
  844. m_state = null;
  845. }
  846. }
  847. m_callback = null;
  848. m_callbackNoResult = null;
  849. }
  850. }
  851. }