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