clockwerk-opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | vero | 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 | } |