4 // Copyright (c) 2008 Jérémie "Garuma" Laval
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights
9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 // copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 using System
.Threading
;
28 using System
.Collections
.Concurrent
;
30 namespace System
.Threading
.Tasks
32 public class Task
: IDisposable
, IAsyncResult
34 // With this attribute each thread has its own value so that it's correct for our Schedule code
35 // and for Parent property.
39 static Action
<Task
> childWorkAdder
;
44 static TaskFactory defaultFactory
= new TaskFactory ();
46 CountdownEvent childTasks
= new CountdownEvent (1);
49 TaskCreationOptions taskCreationOptions
;
52 TaskScheduler taskScheduler
;
54 volatile AggregateException exception
;
55 volatile bool exceptionObserved
;
56 volatile TaskStatus status
;
58 Action
<object> action
;
60 EventHandler completed
;
62 CancellationToken token
;
64 public Task (Action action
) : this (action
, TaskCreationOptions
.None
)
69 public Task (Action action
, TaskCreationOptions options
) : this (action
, CancellationToken
.None
, options
)
74 public Task (Action action
, CancellationToken token
) : this (action
, token
, TaskCreationOptions
.None
)
79 public Task (Action action
, CancellationToken token
, TaskCreationOptions options
)
80 : this ((o
) => { if (action != null) action (); }
, null, token
, options
)
84 public Task (Action
<object> action
, object state
) : this (action
, state
, TaskCreationOptions
.None
)
88 public Task (Action
<object> action
, object state
, TaskCreationOptions options
)
89 : this (action
, state
, CancellationToken
.None
, options
)
93 public Task (Action
<object> action
, object state
, CancellationToken token
)
94 : this (action
, state
, token
, TaskCreationOptions
.None
)
98 public Task (Action
<object> action
, object state
, CancellationToken token
, TaskCreationOptions options
)
100 this.taskCreationOptions
= options
;
101 this.action
= action
== null ? EmptyFunc
: action
;
103 this.taskId
= Interlocked
.Increment (ref id
);
104 this.status
= TaskStatus
.Created
;
107 // Process taskCreationOptions
108 if (CheckTaskOptions (taskCreationOptions
, TaskCreationOptions
.AttachedToParent
)) {
117 if (exception
!= null && !exceptionObserved
)
121 bool CheckTaskOptions (TaskCreationOptions opt
, TaskCreationOptions member
)
123 return (opt
& member
) == member
;
126 static void EmptyFunc (object o
)
133 Start (TaskScheduler
.Current
);
136 public void Start (TaskScheduler tscheduler
)
138 this.taskScheduler
= tscheduler
;
139 Start (ProxifyScheduler (tscheduler
));
142 void Start (IScheduler scheduler
)
144 this.scheduler
= scheduler
;
145 status
= TaskStatus
.WaitingForActivation
;
149 IScheduler
ProxifyScheduler (TaskScheduler tscheduler
)
151 IScheduler sched
= tscheduler
as IScheduler
;
152 return sched
!= null ? sched
: new SchedulerProxy (tscheduler
);
155 public void RunSynchronously ()
157 RunSynchronously (TaskScheduler
.Current
);
160 public void RunSynchronously (TaskScheduler tscheduler
)
162 // Adopt this scheme for the moment
168 public Task
ContinueWith (Action
<Task
> a
)
170 return ContinueWith (a
, TaskContinuationOptions
.None
);
173 public Task
ContinueWith (Action
<Task
> a
, TaskContinuationOptions kind
)
175 return ContinueWith (a
, CancellationToken
.None
, kind
, TaskScheduler
.Current
);
178 public Task
ContinueWith (Action
<Task
> a
, CancellationToken token
)
180 return ContinueWith (a
, token
, TaskContinuationOptions
.None
, TaskScheduler
.Current
);
183 public Task
ContinueWith (Action
<Task
> a
, TaskScheduler scheduler
)
185 return ContinueWith (a
, CancellationToken
.None
, TaskContinuationOptions
.None
, scheduler
);
188 public Task
ContinueWith (Action
<Task
> a
, CancellationToken token
, TaskContinuationOptions kind
, TaskScheduler scheduler
)
190 Task continuation
= new Task ((o
) => a ((Task
)o
), this, token
, GetCreationOptions (kind
));
191 ContinueWithCore (continuation
, kind
, scheduler
);
195 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> a
)
197 return ContinueWith
<TResult
> (a
, TaskContinuationOptions
.None
);
200 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> a
, TaskContinuationOptions options
)
202 return ContinueWith
<TResult
> (a
, CancellationToken
.None
, options
, TaskScheduler
.Current
);
205 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> a
, CancellationToken token
)
207 return ContinueWith
<TResult
> (a
, token
, TaskContinuationOptions
.None
, TaskScheduler
.Current
);
210 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> a
, TaskScheduler scheduler
)
212 return ContinueWith
<TResult
> (a
, CancellationToken
.None
, TaskContinuationOptions
.None
, scheduler
);
215 public Task
<TResult
> ContinueWith
<TResult
> (Func
<Task
, TResult
> a
, CancellationToken token
,
216 TaskContinuationOptions kind
, TaskScheduler scheduler
)
218 Task
<TResult
> t
= new Task
<TResult
> ((o
) => a ((Task
)o
), this, token
, GetCreationOptions (kind
));
220 ContinueWithCore (t
, kind
, scheduler
);
225 internal void ContinueWithCore (Task continuation
, TaskContinuationOptions kind
, TaskScheduler scheduler
)
227 ContinueWithCore (continuation
, kind
, scheduler
, () => true);
230 internal void ContinueWithCore (Task continuation
, TaskContinuationOptions kind
,
231 TaskScheduler scheduler
, Func
<bool> predicate
)
233 // Already set the scheduler so that user can call Wait and that sort of stuff
234 continuation
.taskScheduler
= scheduler
;
235 continuation
.scheduler
= ProxifyScheduler (scheduler
);
237 AtomicBoolean launched
= new AtomicBoolean ();
238 EventHandler action
= delegate (object sender
, EventArgs e
) {
239 if (!predicate ()) return;
241 if (!launched
.Value
&& launched
.TrySet ()) {
242 if (!ContinuationStatusCheck (kind
)) {
243 continuation
.CancelReal ();
244 continuation
.Dispose ();
249 CheckAndSchedule (continuation
, kind
, scheduler
, sender
== null);
254 action (null, EventArgs
.Empty
);
260 // Retry in case completion was achieved but event adding was too late
262 action (null, EventArgs
.Empty
);
265 bool ContinuationStatusCheck (TaskContinuationOptions kind
)
267 if (kind
== TaskContinuationOptions
.None
)
270 int kindCode
= (int)kind
;
272 if (kindCode
>= ((int)TaskContinuationOptions
.NotOnRanToCompletion
)) {
273 if (status
== TaskStatus
.Canceled
) {
274 if (kind
== TaskContinuationOptions
.NotOnCanceled
)
276 if (kind
== TaskContinuationOptions
.OnlyOnFaulted
)
278 if (kind
== TaskContinuationOptions
.OnlyOnRanToCompletion
)
280 } else if (status
== TaskStatus
.Faulted
) {
281 if (kind
== TaskContinuationOptions
.NotOnFaulted
)
283 if (kind
== TaskContinuationOptions
.OnlyOnCanceled
)
285 if (kind
== TaskContinuationOptions
.OnlyOnRanToCompletion
)
287 } else if (status
== TaskStatus
.RanToCompletion
) {
288 if (kind
== TaskContinuationOptions
.NotOnRanToCompletion
)
290 if (kind
== TaskContinuationOptions
.OnlyOnFaulted
)
292 if (kind
== TaskContinuationOptions
.OnlyOnCanceled
)
300 void CheckAndSchedule (Task continuation
, TaskContinuationOptions options
, TaskScheduler scheduler
, bool fromCaller
)
303 && (options
== TaskContinuationOptions
.None
|| (options
& TaskContinuationOptions
.ExecuteSynchronously
) > 0))
304 continuation
.ThreadStart ();
306 continuation
.Start (scheduler
);
309 internal TaskCreationOptions
GetCreationOptions (TaskContinuationOptions kind
)
311 TaskCreationOptions options
= TaskCreationOptions
.None
;
312 if ((kind
& TaskContinuationOptions
.AttachedToParent
) > 0)
313 options
|= TaskCreationOptions
.AttachedToParent
;
314 if ((kind
& TaskContinuationOptions
.PreferFairness
) > 0)
315 options
|= TaskCreationOptions
.PreferFairness
;
316 if ((kind
& TaskContinuationOptions
.LongRunning
) > 0)
317 options
|= TaskCreationOptions
.LongRunning
;
323 #region Internal and protected thingies
324 internal void Schedule ()
326 status
= TaskStatus
.WaitingToRun
;
328 // If worker is null it means it is a local one, revert to the old behavior
329 if (childWorkAdder
== null || CheckTaskOptions (taskCreationOptions
, TaskCreationOptions
.PreferFairness
)) {
330 scheduler
.AddWork (this);
332 /* Like the semantic of the ABP paper describe it, we add ourselves to the bottom
333 * of our Parent Task's ThreadWorker deque. It's ok to do that since we are in
334 * the correct Thread during the creation
336 childWorkAdder (this);
343 TaskScheduler
.Current
= taskScheduler
;
345 if (!token
.IsCancellationRequested
) {
347 status
= TaskStatus
.Running
;
351 } catch (Exception e
) {
352 exception
= new AggregateException (e
);
353 status
= TaskStatus
.Faulted
;
354 if (taskScheduler
.FireUnobservedEvent (exception
).Observed
)
355 exceptionObserved
= true;
364 internal void Execute (Action
<Task
> childAdder
)
366 childWorkAdder
= childAdder
;
370 internal void AddChild ()
372 childTasks
.AddCount ();
375 internal void ChildCompleted ()
377 childTasks
.Signal ();
378 if (childTasks
.IsSet
&& status
== TaskStatus
.WaitingForChildrenToComplete
) {
379 status
= TaskStatus
.RanToCompletion
;
381 // Let continuation creation process
382 EventHandler tempCompleted
= completed
;
383 if (tempCompleted
!= null)
384 tempCompleted (this, EventArgs
.Empty
);
388 internal virtual void InnerInvoke ()
392 // Set action to null so that the GC can collect the delegate and thus
393 // any big object references that the user might have captured in an anonymous method
398 internal void Finish ()
400 // If there wasn't any child created in the task we set the CountdownEvent
401 childTasks
.Signal ();
403 // Don't override Canceled or Faulted
404 if (status
== TaskStatus
.Running
) {
405 if (childTasks
.IsSet
)
406 status
= TaskStatus
.RanToCompletion
;
408 status
= TaskStatus
.WaitingForChildrenToComplete
;
411 if (status
!= TaskStatus
.WaitingForChildrenToComplete
) {
412 // Let continuation creation process
413 EventHandler tempCompleted
= completed
;
414 if (tempCompleted
!= null)
415 tempCompleted (this, EventArgs
.Empty
);
418 // Reset the current thingies
420 TaskScheduler
.Current
= null;
422 // Tell parent that we are finished
423 if (CheckTaskOptions (taskCreationOptions
, TaskCreationOptions
.AttachedToParent
) && parent
!= null){
424 parent
.ChildCompleted ();
431 #region Cancel and Wait related method
433 internal void CancelReal ()
435 exception
= new AggregateException (new TaskCanceledException (this));
436 status
= TaskStatus
.Canceled
;
441 if (scheduler
== null)
442 throw new InvalidOperationException ("The Task hasn't been Started and thus can't be waited on");
444 scheduler
.ParticipateUntil (this);
445 if (exception
!= null)
449 public void Wait (CancellationToken token
)
454 public bool Wait (TimeSpan ts
)
456 return Wait ((int)ts
.TotalMilliseconds
, CancellationToken
.None
);
459 public bool Wait (int millisecondsTimeout
)
461 return Wait (millisecondsTimeout
, CancellationToken
.None
);
464 public bool Wait (int millisecondsTimeout
, CancellationToken token
)
466 Watch sw
= Watch
.StartNew ();
467 return Wait (() => sw
.ElapsedMilliseconds
>= millisecondsTimeout
, token
);
470 bool Wait (Func
<bool> stopFunc
, CancellationToken token
)
472 if (scheduler
== null)
473 throw new InvalidOperationException ("The Task hasn't been Started and thus can't be waited on");
475 bool result
= scheduler
.ParticipateUntil (this, delegate {
476 if (token
.IsCancellationRequested
)
477 throw new OperationCanceledException ("The CancellationToken has had cancellation requested.");
479 return (stopFunc
!= null) ? stopFunc () : false;
482 if (exception
!= null)
488 public static void WaitAll (params Task
[] tasks
)
491 throw new ArgumentNullException ("tasks");
492 if (tasks
.Length
== 0)
493 throw new ArgumentException ("tasks is empty", "tasks");
495 foreach (var t
in tasks
)
499 public static void WaitAll (Task
[] tasks
, CancellationToken token
)
502 throw new ArgumentNullException ("tasks");
503 if (tasks
.Length
== 0)
504 throw new ArgumentException ("tasks is empty", "tasks");
506 foreach (var t
in tasks
)
510 public static bool WaitAll (Task
[] tasks
, TimeSpan ts
)
513 throw new ArgumentNullException ("tasks");
514 if (tasks
.Length
== 0)
515 throw new ArgumentException ("tasks is empty", "tasks");
518 foreach (var t
in tasks
)
519 result
&= t
.Wait (ts
);
523 public static bool WaitAll (Task
[] tasks
, int millisecondsTimeout
)
526 throw new ArgumentNullException ("tasks");
527 if (tasks
.Length
== 0)
528 throw new ArgumentException ("tasks is empty", "tasks");
531 foreach (var t
in tasks
)
532 result
&= t
.Wait (millisecondsTimeout
);
536 public static bool WaitAll (Task
[] tasks
, int millisecondsTimeout
, CancellationToken token
)
539 throw new ArgumentNullException ("tasks");
540 if (tasks
.Length
== 0)
541 throw new ArgumentException ("tasks is empty", "tasks");
544 foreach (var t
in tasks
)
545 result
&= t
.Wait (millisecondsTimeout
, token
);
549 public static int WaitAny (params Task
[] tasks
)
551 return WaitAny (tasks
, null, null);
554 static int WaitAny (Task
[] tasks
, Func
<bool> stopFunc
, CancellationToken
? token
)
557 throw new ArgumentNullException ("tasks");
558 if (tasks
.Length
== 0)
559 throw new ArgumentException ("tasks is empty", "tasks");
562 int indexFirstFinished
= -1;
565 foreach (Task t
in tasks
) {
566 t
.ContinueWith (delegate {
567 int indexResult
= index
;
568 int result
= Interlocked
.Increment (ref numFinished
);
569 // Check if we are the first to have finished
571 indexFirstFinished
= indexResult
;
576 // One task already finished
577 if (indexFirstFinished
!= -1)
578 return indexFirstFinished
;
580 // All tasks are supposed to use the same TaskManager
581 tasks
[0].scheduler
.ParticipateUntil (delegate {
582 if (stopFunc
!= null && stopFunc ())
585 if (token
.HasValue
&& token
.Value
.IsCancellationRequested
)
586 throw new OperationCanceledException ("The CancellationToken has had cancellation requested.");
588 return numFinished
>= 1;
591 return indexFirstFinished
;
594 public static int WaitAny (Task
[] tasks
, TimeSpan ts
)
596 return WaitAny (tasks
, (int)ts
.TotalMilliseconds
);
599 public static int WaitAny (Task
[] tasks
, int millisecondsTimeout
)
601 if (millisecondsTimeout
< -1)
602 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
604 if (millisecondsTimeout
== -1)
605 return WaitAny (tasks
);
607 Watch sw
= Watch
.StartNew ();
608 return WaitAny (tasks
, () => sw
.ElapsedMilliseconds
> millisecondsTimeout
, null);
611 public static int WaitAny (Task
[] tasks
, int millisecondsTimeout
, CancellationToken token
)
613 if (millisecondsTimeout
< -1)
614 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
616 if (millisecondsTimeout
== -1)
617 return WaitAny (tasks
);
619 Watch sw
= Watch
.StartNew ();
620 return WaitAny (tasks
, () => sw
.ElapsedMilliseconds
> millisecondsTimeout
, token
);
623 public static int WaitAny (Task
[] tasks
, CancellationToken token
)
625 return WaitAny (tasks
, null, token
);
630 public void Dispose ()
635 protected virtual void Dispose (bool disposeManagedRes
)
637 // Set action to null so that the GC can collect the delegate and thus
638 // any big object references that the user might have captured in a anonymous method
639 if (disposeManagedRes
) {
648 public static TaskFactory Factory
{
650 return defaultFactory
;
654 public static int? CurrentId
{
657 return t
== null ? (int?)null : t
.Id
;
661 public AggregateException Exception
{
663 exceptionObserved
= true;
672 public bool IsCanceled
{
674 return status
== TaskStatus
.Canceled
;
678 public bool IsCompleted
{
680 return status
== TaskStatus
.RanToCompletion
||
681 status
== TaskStatus
.Canceled
|| status
== TaskStatus
.Faulted
;
685 public bool IsFaulted
{
687 return status
== TaskStatus
.Faulted
;
691 public TaskCreationOptions CreationOptions
{
693 return taskCreationOptions
;
697 public TaskStatus Status
{
706 public object AsyncState
{
712 bool IAsyncResult
.CompletedSynchronously
{
718 WaitHandle IAsyncResult
.AsyncWaitHandle
{