3 // Copyright (c) 2008 Jérémie "Garuma" Laval
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28 using System
.Threading
;
29 using System
.Collections
.Concurrent
;
31 namespace System
.Threading
.Tasks
33 [System
.Diagnostics
.DebuggerDisplay ("Id = {Id}, Status = {Status}, Method = {DebuggerDisplayMethodDescription}")]
34 [System
.Diagnostics
.DebuggerTypeProxy ("System.Threading.Tasks.SystemThreadingTasks_TaskDebugView")]
35 public class Task
: IDisposable
, IAsyncResult
37 // With this attribute each thread has its own value so that it's correct for our Schedule code
38 // and for Parent property.
42 static Action
<Task
> childWorkAdder
;
47 static TaskFactory defaultFactory
= new TaskFactory ();
49 CountdownEvent childTasks
= new CountdownEvent (1);
52 TaskCreationOptions taskCreationOptions
;
54 TaskScheduler scheduler
;
56 ManualResetEventSlim schedWait
= new ManualResetEventSlim (false);
58 volatile AggregateException exception
;
59 volatile bool exceptionObserved
;
63 Action
<object> action
;
66 AtomicBooleanValue executing
;
68 ConcurrentQueue
<EventHandler
> completed
;
70 CancellationToken token
;
72 public Task (Action action
) : this (action
, TaskCreationOptions
.None
)
77 public Task (Action action
, TaskCreationOptions creationOptions
) : this (action
, CancellationToken
.None
, creationOptions
)
82 public Task (Action action
, CancellationToken cancellationToken
) : this (action
, cancellationToken
, TaskCreationOptions
.None
)
87 public Task (Action action
, CancellationToken cancellationToken
, TaskCreationOptions creationOptions
)
88 : this (null, null, cancellationToken
, creationOptions
)
90 this.simpleAction
= action
;
93 public Task (Action
<object> action
, object state
) : this (action
, state
, TaskCreationOptions
.None
)
97 public Task (Action
<object> action
, object state
, TaskCreationOptions creationOptions
)
98 : this (action
, state
, CancellationToken
.None
, creationOptions
)
102 public Task (Action
<object> action
, object state
, CancellationToken cancellationToken
)
103 : this (action
, state
, cancellationToken
, TaskCreationOptions
.None
)
107 public Task (Action
<object> action
, object state
, CancellationToken cancellationToken
, TaskCreationOptions creationOptions
)
108 : this (action
, state
, cancellationToken
, creationOptions
, current
)
113 internal Task (Action
<object> action
,
115 CancellationToken cancellationToken
,
116 TaskCreationOptions creationOptions
,
119 this.taskCreationOptions
= creationOptions
;
120 this.action
= action
;
122 this.taskId
= Interlocked
.Increment (ref id
);
123 this.status
= TaskStatus
.Created
;
124 this.token
= cancellationToken
;
125 this.parent
= parent
;
127 // Process taskCreationOptions
128 if (CheckTaskOptions (taskCreationOptions
, TaskCreationOptions
.AttachedToParent
) && parent
!= null)
134 if (exception
!= null && !exceptionObserved
)
138 bool CheckTaskOptions (TaskCreationOptions opt
, TaskCreationOptions member
)
140 return (opt
& member
) == member
;
146 Start (TaskScheduler
.Current
);
149 public void Start (TaskScheduler scheduler
)
151 SetupScheduler (scheduler
);
155 internal void SetupScheduler (TaskScheduler scheduler
)
157 this.scheduler
= scheduler
;
158 status
= TaskStatus
.WaitingForActivation
;
162 public void RunSynchronously ()
164 RunSynchronously (TaskScheduler
.Current
);
167 public void RunSynchronously (TaskScheduler scheduler
)
169 if (this.Status
!= TaskStatus
.Created
)
170 throw new InvalidOperationException ("The task is not in a valid state to be started");
171 if (scheduler
.TryExecuteTask (this))
180 public Task
ContinueWith (Action
<Task
> continuationAction
)
182 return ContinueWith (continuationAction
, TaskContinuationOptions
.None
);
185 public Task
ContinueWith (Action
<Task
> continuationAction
, TaskContinuationOptions continuationOptions
)
187 return ContinueWith (continuationAction
, CancellationToken
.None
, continuationOptions
, TaskScheduler
.Current
);
190 public Task
ContinueWith (Action
<Task
> continuationAction
, CancellationToken cancellationToken
)
192 return ContinueWith (continuationAction
, cancellationToken
, TaskContinuationOptions
.None
, TaskScheduler
.Current
);
195 public Task
ContinueWith (Action
<Task
> continuationAction
, TaskScheduler scheduler
)
197 return ContinueWith (continuationAction
, CancellationToken
.None
, TaskContinuationOptions
.None
, scheduler
);
200 public Task
ContinueWith (Action
<Task
> continuationAction
, CancellationToken cancellationToken
, TaskContinuationOptions continuationOptions
, TaskScheduler scheduler
)
202 Task continuation
= new Task ((o
) => continuationAction ((Task
)o
),
205 GetCreationOptions (continuationOptions
),
207 ContinueWithCore (continuation
, continuationOptions
, scheduler
);
212 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> continuationFunction
)
214 return ContinueWith
<TResult
> (continuationFunction
, TaskContinuationOptions
.None
);
217 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> continuationFunction
, TaskContinuationOptions continuationOptions
)
219 return ContinueWith
<TResult
> (continuationFunction
, CancellationToken
.None
, continuationOptions
, TaskScheduler
.Current
);
222 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> continuationFunction
, CancellationToken cancellationToken
)
224 return ContinueWith
<TResult
> (continuationFunction
, cancellationToken
, TaskContinuationOptions
.None
, TaskScheduler
.Current
);
227 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> continuationFunction
, TaskScheduler scheduler
)
229 return ContinueWith
<TResult
> (continuationFunction
, CancellationToken
.None
, TaskContinuationOptions
.None
, scheduler
);
232 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> continuationFunction
, CancellationToken cancellationToken
,
233 TaskContinuationOptions continuationOptions
, TaskScheduler scheduler
)
235 if (continuationFunction
== null)
236 throw new ArgumentNullException ("continuationFunction");
237 if (scheduler
== null)
238 throw new ArgumentNullException ("scheduler");
240 Task
<TResult
> t
= new Task
<TResult
> ((o
) => continuationFunction ((Task
)o
),
243 GetCreationOptions (continuationOptions
),
246 ContinueWithCore (t
, continuationOptions
, scheduler
);
251 internal void ContinueWithCore (Task continuation
, TaskContinuationOptions continuationOptions
, TaskScheduler scheduler
)
253 ContinueWithCore (continuation
, continuationOptions
, scheduler
, () => true);
256 internal void ContinueWithCore (Task continuation
, TaskContinuationOptions kind
,
257 TaskScheduler scheduler
, Func
<bool> predicate
)
259 // Already set the scheduler so that user can call Wait and that sort of stuff
260 continuation
.scheduler
= scheduler
;
261 continuation
.schedWait
.Set ();
262 continuation
.status
= TaskStatus
.WaitingForActivation
;
264 AtomicBoolean launched
= new AtomicBoolean ();
265 EventHandler action
= delegate (object sender
, EventArgs e
) {
266 if (launched
.TryRelaxedSet ()) {
270 if (!ContinuationStatusCheck (kind
)) {
271 continuation
.CancelReal ();
272 continuation
.Dispose ();
277 CheckAndSchedule (continuation
, kind
, scheduler
, sender
== null);
282 action (null, EventArgs
.Empty
);
286 if (completed
== null)
287 Interlocked
.CompareExchange (ref completed
, new ConcurrentQueue
<EventHandler
> (), null);
288 completed
.Enqueue (action
);
290 // Retry in case completion was achieved but event adding was too late
292 action (null, EventArgs
.Empty
);
296 bool ContinuationStatusCheck (TaskContinuationOptions kind
)
298 if (kind
== TaskContinuationOptions
.None
)
301 int kindCode
= (int)kind
;
303 if (kindCode
>= ((int)TaskContinuationOptions
.NotOnRanToCompletion
)) {
304 if (status
== TaskStatus
.Canceled
) {
305 if ((kind
& TaskContinuationOptions
.NotOnCanceled
) > 0)
307 if ((kind
& TaskContinuationOptions
.OnlyOnFaulted
) > 0)
309 if ((kind
& TaskContinuationOptions
.OnlyOnRanToCompletion
) > 0)
311 } else if (status
== TaskStatus
.Faulted
) {
312 if ((kind
& TaskContinuationOptions
.NotOnFaulted
) > 0)
314 if ((kind
& TaskContinuationOptions
.OnlyOnCanceled
) > 0)
316 if ((kind
& TaskContinuationOptions
.OnlyOnRanToCompletion
) > 0)
318 } else if (status
== TaskStatus
.RanToCompletion
) {
319 if ((kind
& TaskContinuationOptions
.NotOnRanToCompletion
) > 0)
321 if ((kind
& TaskContinuationOptions
.OnlyOnFaulted
) > 0)
323 if ((kind
& TaskContinuationOptions
.OnlyOnCanceled
) > 0)
331 void CheckAndSchedule (Task continuation
, TaskContinuationOptions options
, TaskScheduler scheduler
, bool fromCaller
)
333 if ((options
& TaskContinuationOptions
.ExecuteSynchronously
) > 0)
334 continuation
.ThreadStart ();
336 continuation
.Start (scheduler
);
339 internal TaskCreationOptions
GetCreationOptions (TaskContinuationOptions kind
)
341 TaskCreationOptions options
= TaskCreationOptions
.None
;
342 if ((kind
& TaskContinuationOptions
.AttachedToParent
) > 0)
343 options
|= TaskCreationOptions
.AttachedToParent
;
344 if ((kind
& TaskContinuationOptions
.PreferFairness
) > 0)
345 options
|= TaskCreationOptions
.PreferFairness
;
346 if ((kind
& TaskContinuationOptions
.LongRunning
) > 0)
347 options
|= TaskCreationOptions
.LongRunning
;
353 #region Internal and protected thingies
354 internal void Schedule ()
356 status
= TaskStatus
.WaitingToRun
;
358 // If worker is null it means it is a local one, revert to the old behavior
359 // If TaskScheduler.Current is not being used, the scheduler was explicitly provided, so we must use that
360 if (scheduler
!= TaskScheduler
.Current
|| childWorkAdder
== null || CheckTaskOptions (taskCreationOptions
, TaskCreationOptions
.PreferFairness
)) {
361 scheduler
.QueueTask (this);
363 /* Like the semantic of the ABP paper describe it, we add ourselves to the bottom
364 * of our Parent Task's ThreadWorker deque. It's ok to do that since we are in
365 * the correct Thread during the creation
367 childWorkAdder (this);
373 /* Allow scheduler to break fairness of deque ordering without
374 * breaking its semantic (the task can be executed twice but the
375 * second time it will return immediately
377 if (!executing
.TryRelaxedSet ())
381 TaskScheduler
.Current
= scheduler
;
383 if (!token
.IsCancellationRequested
) {
385 status
= TaskStatus
.Running
;
389 } catch (OperationCanceledException oce
) {
390 if (oce
.CancellationToken
== token
)
393 HandleGenericException (oce
);
394 } catch (Exception e
) {
395 HandleGenericException (e
);
404 internal void Execute (Action
<Task
> childAdder
)
406 childWorkAdder
= childAdder
;
410 internal void AddChild ()
412 childTasks
.AddCount ();
415 internal void ChildCompleted ()
417 if (childTasks
.Signal () && status
== TaskStatus
.WaitingForChildrenToComplete
) {
418 status
= TaskStatus
.RanToCompletion
;
420 ProcessCompleteDelegates ();
424 internal virtual void InnerInvoke ()
426 if (action
== null && simpleAction
!= null)
428 else if (action
!= null)
430 // Set action to null so that the GC can collect the delegate and thus
431 // any big object references that the user might have captured in an anonymous method
437 internal void Finish ()
439 // If there wasn't any child created in the task we set the CountdownEvent
440 childTasks
.Signal ();
442 // Don't override Canceled or Faulted
443 if (status
== TaskStatus
.Running
) {
444 if (childTasks
.IsSet
)
445 status
= TaskStatus
.RanToCompletion
;
447 status
= TaskStatus
.WaitingForChildrenToComplete
;
450 if (status
!= TaskStatus
.WaitingForChildrenToComplete
)
451 ProcessCompleteDelegates ();
453 // Reset the current thingies
455 TaskScheduler
.Current
= null;
457 // Tell parent that we are finished
458 if (CheckTaskOptions (taskCreationOptions
, TaskCreationOptions
.AttachedToParent
) && parent
!= null) {
459 parent
.ChildCompleted ();
463 void ProcessCompleteDelegates ()
465 if (completed
== null)
468 EventHandler handler
;
469 while (completed
.TryDequeue (out handler
))
470 handler (this, EventArgs
.Empty
);
474 #region Cancel and Wait related method
476 internal void CancelReal ()
478 status
= TaskStatus
.Canceled
;
481 internal void HandleGenericException (Exception e
)
483 HandleGenericException (new AggregateException (e
));
486 internal void HandleGenericException (AggregateException e
)
489 status
= TaskStatus
.Faulted
;
490 if (scheduler
!= null && scheduler
.FireUnobservedEvent (exception
).Observed
)
491 exceptionObserved
= true;
496 if (scheduler
== null)
500 scheduler
.ParticipateUntil (this);
501 if (exception
!= null)
504 throw new AggregateException (new TaskCanceledException (this));
507 public void Wait (CancellationToken cancellationToken
)
509 Wait (-1, cancellationToken
);
512 public bool Wait (TimeSpan timeout
)
514 return Wait (CheckTimeout (timeout
), CancellationToken
.None
);
517 public bool Wait (int millisecondsTimeout
)
519 return Wait (millisecondsTimeout
, CancellationToken
.None
);
522 public bool Wait (int millisecondsTimeout
, CancellationToken cancellationToken
)
524 if (millisecondsTimeout
< -1)
525 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
527 if (millisecondsTimeout
== -1 && token
== CancellationToken
.None
) {
532 Watch watch
= Watch
.StartNew ();
534 if (scheduler
== null) {
535 schedWait
.Wait (millisecondsTimeout
, cancellationToken
);
536 millisecondsTimeout
= ComputeTimeout (millisecondsTimeout
, watch
);
539 ManualResetEventSlim predicateEvt
= new ManualResetEventSlim (false);
540 if (cancellationToken
!= CancellationToken
.None
) {
541 cancellationToken
.Register (predicateEvt
.Set
);
542 cancellationToken
.ThrowIfCancellationRequested ();
545 bool result
= scheduler
.ParticipateUntil (this, predicateEvt
, millisecondsTimeout
);
547 if (exception
!= null)
550 throw new AggregateException (new TaskCanceledException (this));
555 public static void WaitAll (params Task
[] tasks
)
558 throw new ArgumentNullException ("tasks");
560 foreach (var t
in tasks
) {
562 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
567 public static void WaitAll (Task
[] tasks
, CancellationToken cancellationToken
)
570 throw new ArgumentNullException ("tasks");
572 foreach (var t
in tasks
) {
574 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
576 t
.Wait (cancellationToken
);
580 public static bool WaitAll (Task
[] tasks
, TimeSpan timeout
)
583 throw new ArgumentNullException ("tasks");
586 foreach (var t
in tasks
) {
588 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
590 result
&= t
.Wait (timeout
);
595 public static bool WaitAll (Task
[] tasks
, int millisecondsTimeout
)
598 throw new ArgumentNullException ("tasks");
601 foreach (var t
in tasks
) {
603 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
605 result
&= t
.Wait (millisecondsTimeout
);
610 public static bool WaitAll (Task
[] tasks
, int millisecondsTimeout
, CancellationToken cancellationToken
)
613 throw new ArgumentNullException ("tasks");
616 foreach (var t
in tasks
) {
618 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
620 result
&= t
.Wait (millisecondsTimeout
, cancellationToken
);
625 public static int WaitAny (params Task
[] tasks
)
627 return WaitAny (tasks
, -1, CancellationToken
.None
);
630 public static int WaitAny (Task
[] tasks
, TimeSpan timeout
)
632 return WaitAny (tasks
, CheckTimeout (timeout
));
635 public static int WaitAny (Task
[] tasks
, int millisecondsTimeout
)
637 if (millisecondsTimeout
< -1)
638 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
640 if (millisecondsTimeout
== -1)
641 return WaitAny (tasks
);
643 return WaitAny (tasks
, millisecondsTimeout
, CancellationToken
.None
);
646 public static int WaitAny (Task
[] tasks
, CancellationToken cancellationToken
)
648 return WaitAny (tasks
, -1, cancellationToken
);
651 public static int WaitAny (Task
[] tasks
, int millisecondsTimeout
, CancellationToken cancellationToken
)
654 throw new ArgumentNullException ("tasks");
655 if (tasks
.Length
== 0)
656 throw new ArgumentException ("tasks is empty", "tasks");
657 if (tasks
.Length
== 1) {
658 tasks
[0].Wait (millisecondsTimeout
, cancellationToken
);
663 int indexFirstFinished
= -1;
665 TaskScheduler sched
= null;
667 Watch watch
= Watch
.StartNew ();
668 ManualResetEventSlim predicateEvt
= new ManualResetEventSlim (false);
670 foreach (Task t
in tasks
) {
671 int indexResult
= index
++;
672 t
.ContinueWith (delegate {
673 if (numFinished
>= 1)
675 int result
= Interlocked
.Increment (ref numFinished
);
677 // Check if we are the first to have finished
679 indexFirstFinished
= indexResult
;
683 }, TaskContinuationOptions
.ExecuteSynchronously
);
685 if (sched
== null && t
.scheduler
!= null) {
691 // If none of task have a scheduler we are forced to wait for at least one to start
693 var handles
= Array
.ConvertAll (tasks
, t
=> t
.schedWait
.WaitHandle
);
695 if ((shandle
= WaitHandle
.WaitAny (handles
, millisecondsTimeout
)) == WaitHandle
.WaitTimeout
)
697 sched
= tasks
[shandle
].scheduler
;
698 task
= tasks
[shandle
];
699 millisecondsTimeout
= ComputeTimeout (millisecondsTimeout
, watch
);
702 // One task already finished
703 if (indexFirstFinished
!= -1)
704 return indexFirstFinished
;
706 if (cancellationToken
!= CancellationToken
.None
) {
707 cancellationToken
.Register (predicateEvt
.Set
);
708 cancellationToken
.ThrowIfCancellationRequested ();
711 sched
.ParticipateUntil (task
, predicateEvt
, millisecondsTimeout
);
713 // Index update is still not done
714 if (indexFirstFinished
== -1) {
715 SpinWait wait
= new SpinWait ();
716 while (indexFirstFinished
== -1)
720 return indexFirstFinished
;
723 static int CheckTimeout (TimeSpan timeout
)
726 return checked ((int)timeout
.TotalMilliseconds
);
727 } catch (System
.OverflowException
) {
728 throw new ArgumentOutOfRangeException ("timeout");
732 static int ComputeTimeout (int millisecondsTimeout
, Watch watch
)
734 return millisecondsTimeout
== -1 ? -1 : (int)Math
.Max (watch
.ElapsedMilliseconds
- millisecondsTimeout
, 1);
740 public void Dispose ()
745 protected virtual void Dispose (bool disposing
)
747 // Set action to null so that the GC can collect the delegate and thus
748 // any big object references that the user might have captured in a anonymous method
757 public static TaskFactory Factory
{
759 return defaultFactory
;
763 public static int? CurrentId
{
766 return t
== null ? (int?)null : t
.Id
;
770 public AggregateException Exception
{
772 exceptionObserved
= true;
781 public bool IsCanceled
{
783 return status
== TaskStatus
.Canceled
;
787 public bool IsCompleted
{
789 return status
== TaskStatus
.RanToCompletion
||
790 status
== TaskStatus
.Canceled
|| status
== TaskStatus
.Faulted
;
794 public bool IsFaulted
{
796 return status
== TaskStatus
.Faulted
;
800 public TaskCreationOptions CreationOptions
{
802 return taskCreationOptions
;
806 public TaskStatus Status
{
815 public object AsyncState
{
821 bool IAsyncResult
.CompletedSynchronously
{
827 WaitHandle IAsyncResult
.AsyncWaitHandle
{
839 internal Task Parent
{