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;
42 static TaskFactory defaultFactory = new TaskFactory ();
44 CountdownEvent childTasks = new CountdownEvent (1);
46 Task parent = current;
49 bool respectParentCancellation;
50 TaskCreationOptions taskCreationOptions;
53 TaskScheduler taskScheduler;
55 volatile Exception exception;
56 volatile bool exceptionObserved;
57 volatile TaskStatus status;
59 Action<object> action;
61 EventHandler completed;
63 CancellationTokenSource src = new CancellationTokenSource ();
66 public Task (Action action) : this (action, TaskCreationOptions.None)
71 public Task (Action action, TaskCreationOptions options) : this ((o) => action (), null, options)
76 public Task (Action<object> action, object state) : this (action, state, TaskCreationOptions.None)
81 public Task (Action<object> action, object state, TaskCreationOptions options)
83 this.taskCreationOptions = options;
84 this.action = action == null ? EmptyFunc : action;
86 this.taskId = Interlocked.Increment (ref id);
87 this.status = TaskStatus.Created;
89 // Process taskCreationOptions
90 if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.DetachedFromParent))
92 else if (parent != null)
95 respectParentCancellation =
96 CheckTaskOptions (taskCreationOptions, TaskCreationOptions.RespectParentCancellation);
101 if (exception != null && !exceptionObserved)
105 bool CheckTaskOptions (TaskCreationOptions opt, TaskCreationOptions member)
107 return (opt & member) == member;
110 static void EmptyFunc (object o)
117 Start (TaskScheduler.Current);
120 public void Start (TaskScheduler tscheduler)
122 this.taskScheduler = tscheduler;
123 Start (ProxifyScheduler (tscheduler));
126 void Start (IScheduler scheduler)
128 this.scheduler = scheduler;
129 status = TaskStatus.WaitingForActivation;
133 IScheduler ProxifyScheduler (TaskScheduler tscheduler)
135 IScheduler sched = tscheduler as IScheduler;
136 return sched != null ? sched : new SchedulerProxy (tscheduler);
139 public void RunSynchronously ()
141 RunSynchronously (TaskScheduler.Current);
144 public void RunSynchronously (TaskScheduler tscheduler)
146 // Adopt this scheme for the moment
152 public Task ContinueWith (Action<Task> a)
154 return ContinueWith (a, TaskContinuationOptions.None);
157 public Task ContinueWith (Action<Task> a, TaskContinuationOptions kind)
159 return ContinueWith (a, kind, TaskScheduler.Current);
162 public Task ContinueWith (Action<Task> a, TaskScheduler scheduler)
164 return ContinueWith (a, TaskContinuationOptions.None, scheduler);
167 public Task ContinueWith (Action<Task> a, TaskContinuationOptions kind, TaskScheduler scheduler)
169 Task continuation = new Task ((o) => a ((Task)o), this, GetCreationOptions (kind));
170 ContinueWithCore (continuation, kind, scheduler);
174 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> a)
176 return ContinueWith<TResult> (a, TaskContinuationOptions.None);
179 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> a, TaskContinuationOptions options)
181 return ContinueWith<TResult> (a, options, TaskScheduler.Current);
184 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> a, TaskScheduler scheduler)
186 return ContinueWith<TResult> (a, TaskContinuationOptions.None, scheduler);
189 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> a, TaskContinuationOptions kind, TaskScheduler scheduler)
191 Task<TResult> t = new Task<TResult> ((o) => a ((Task)o), this, GetCreationOptions (kind));
193 ContinueWithCore (t, kind, scheduler);
198 internal void ContinueWithCore (Task continuation, TaskContinuationOptions kind, TaskScheduler scheduler)
200 ContinueWithCore (continuation, kind, scheduler, () => true);
203 internal void ContinueWithCore (Task continuation, TaskContinuationOptions kind,
204 TaskScheduler scheduler, Func<bool> predicate)
206 // Already set the scheduler so that user can call Wait and that sort of stuff
207 continuation.taskScheduler = scheduler;
208 continuation.scheduler = ProxifyScheduler (scheduler);
210 AtomicBoolean launched = new AtomicBoolean ();
211 EventHandler action = delegate {
212 if (!predicate ()) return;
214 if (!launched.Value && !launched.Exchange (true)) {
215 if (!ContinuationStatusCheck (kind)) {
216 continuation.Cancel ();
217 continuation.CancelReal ();
218 continuation.Dispose ();
223 CheckAndSchedule (continuation, kind, scheduler);
228 action (this, EventArgs.Empty);
234 // Retry in case completion was achieved but event adding was too late
236 action (this, EventArgs.Empty);
239 bool ContinuationStatusCheck (TaskContinuationOptions kind)
241 if (kind == TaskContinuationOptions.None)
244 int kindCode = (int)kind;
246 if (kindCode >= ((int)TaskContinuationOptions.NotOnRanToCompletion)) {
247 if (status == TaskStatus.Canceled) {
248 if (kind == TaskContinuationOptions.NotOnCanceled)
250 if (kind == TaskContinuationOptions.OnlyOnFaulted)
252 if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
254 } else if (status == TaskStatus.Faulted) {
255 if (kind == TaskContinuationOptions.NotOnFaulted)
257 if (kind == TaskContinuationOptions.OnlyOnCanceled)
259 if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
261 } else if (status == TaskStatus.RanToCompletion) {
262 if (kind == TaskContinuationOptions.NotOnRanToCompletion)
264 if (kind == TaskContinuationOptions.OnlyOnFaulted)
266 if (kind == TaskContinuationOptions.OnlyOnCanceled)
274 void CheckAndSchedule (Task continuation, TaskContinuationOptions options, TaskScheduler scheduler)
276 if (options == TaskContinuationOptions.None || (options & TaskContinuationOptions.ExecuteSynchronously) > 0) {
277 continuation.ThreadStart ();
279 continuation.Start (scheduler);
283 static TaskCreationOptions GetCreationOptions (TaskContinuationOptions kind)
285 TaskCreationOptions options = TaskCreationOptions.None;
286 if ((kind & TaskContinuationOptions.DetachedFromParent) > 0)
287 options |= TaskCreationOptions.DetachedFromParent;
288 if ((kind & TaskContinuationOptions.RespectParentCancellation) > 0)
289 options |= TaskCreationOptions.RespectParentCancellation;
295 #region Internal and protected thingies
296 internal void Schedule ()
298 status = TaskStatus.WaitingToRun;
300 // If worker is null it means it is a local one, revert to the old behavior
301 if (current == null || childWorkAdder == null || parent == null
302 || CheckTaskOptions (taskCreationOptions, TaskCreationOptions.PreferFairness)) {
304 scheduler.AddWork (this);
307 /* Like the semantic of the ABP paper describe it, we add ourselves to the bottom
308 * of our Parent Task's ThreadWorker deque. It's ok to do that since we are in
309 * the correct Thread during the creation
311 childWorkAdder (this);
318 TaskScheduler.Current = taskScheduler;
320 if (!src.IsCancellationRequested
321 && (!respectParentCancellation || (respectParentCancellation && parent != null && !parent.IsCanceled))) {
323 status = TaskStatus.Running;
327 } catch (Exception e) {
329 status = TaskStatus.Faulted;
332 AcknowledgeCancellation ();
338 internal void Execute (Action<Task> childAdder)
340 childWorkAdder = childAdder;
344 internal void AddChild ()
346 childTasks.AddCount ();
349 internal void ChildCompleted ()
351 childTasks.Signal ();
352 if (childTasks.IsSet && status == TaskStatus.WaitingForChildrenToComplete)
353 status = TaskStatus.RanToCompletion;
356 internal virtual void InnerInvoke ()
360 // Set action to null so that the GC can collect the delegate and thus
361 // any big object references that the user might have captured in an anonymous method
366 internal void Finish ()
368 // If there wasn't any child created in the task we set the CountdownEvent
369 childTasks.Signal ();
371 // Don't override Canceled or Faulted
372 if (status == TaskStatus.Running) {
373 if (childTasks.IsSet )
374 status = TaskStatus.RanToCompletion;
376 status = TaskStatus.WaitingForChildrenToComplete;
379 // Call the event in the correct style
380 EventHandler tempCompleted = completed;
381 if (tempCompleted != null)
382 tempCompleted (this, EventArgs.Empty);
384 // Reset the current thingies
386 TaskScheduler.Current = null;
388 // Tell parent that we are finished
389 if (!CheckTaskOptions (taskCreationOptions, TaskCreationOptions.DetachedFromParent) && parent != null){
390 parent.ChildCompleted ();
397 #region Cancel and Wait related methods
398 public void AcknowledgeCancellation ()
401 throw new InvalidOperationException ("The Task object is different from the currently executing"
402 + "task or the current task hasn't been "
403 + "marked for cancellation.");
408 internal void CancelReal ()
410 exception = new TaskCanceledException (this);
411 status = TaskStatus.Canceled;
414 public void Cancel ()
419 public void CancelAndWait ()
425 public void CancelAndWait (CancellationToken token)
431 public bool CancelAndWait (TimeSpan ts)
437 public bool CancelAndWait (int millisecondsTimeout)
440 return Wait (millisecondsTimeout);
443 public bool CancelAndWait (int millisecondsTimeout, CancellationToken token)
446 return Wait (millisecondsTimeout, token);
451 if (scheduler == null)
452 throw new InvalidOperationException ("The Task hasn't been Started and thus can't be waited on");
454 scheduler.ParticipateUntil (this);
455 if (exception != null && !(exception is TaskCanceledException))
459 public void Wait (CancellationToken token)
464 public bool Wait (TimeSpan ts)
466 return Wait ((int)ts.TotalMilliseconds);
469 public bool Wait (int millisecondsTimeout)
471 Watch sw = Watch.StartNew ();
472 return Wait (() => sw.ElapsedMilliseconds >= millisecondsTimeout, null);
475 public bool Wait (int millisecondsTimeout, CancellationToken token)
477 Watch sw = Watch.StartNew ();
478 return Wait (() => sw.ElapsedMilliseconds >= millisecondsTimeout, token);
481 bool Wait (Func<bool> stopFunc, CancellationToken? token)
483 if (scheduler == null)
484 throw new InvalidOperationException ("The Task hasn't been Started and thus can't be waited on");
486 bool result = scheduler.ParticipateUntil (this, delegate {
487 if (token.HasValue && token.Value.IsCancellationRequested)
488 throw new OperationCanceledException ("The CancellationToken has had cancellation requested.");
491 return (stopFunc != null) ? stopFunc () : false;
494 if (exception != null && !(exception is TaskCanceledException))
500 public static void WaitAll (params Task[] tasks)
503 throw new ArgumentNullException ("tasks");
504 if (tasks.Length == 0)
505 throw new ArgumentException ("tasks is empty", "tasks");
507 foreach (var t in tasks)
511 public static void WaitAll (Task[] tasks, CancellationToken token)
514 throw new ArgumentNullException ("tasks");
515 if (tasks.Length == 0)
516 throw new ArgumentException ("tasks is empty", "tasks");
518 foreach (var t in tasks)
522 public static bool WaitAll (Task[] tasks, TimeSpan ts)
525 throw new ArgumentNullException ("tasks");
526 if (tasks.Length == 0)
527 throw new ArgumentException ("tasks is empty", "tasks");
530 foreach (var t in tasks)
531 result &= t.Wait (ts);
535 public static bool WaitAll (Task[] tasks, int millisecondsTimeout)
538 throw new ArgumentNullException ("tasks");
539 if (tasks.Length == 0)
540 throw new ArgumentException ("tasks is empty", "tasks");
543 foreach (var t in tasks)
544 result &= t.Wait (millisecondsTimeout);
548 public static bool WaitAll (Task[] tasks, int millisecondsTimeout, CancellationToken token)
551 throw new ArgumentNullException ("tasks");
552 if (tasks.Length == 0)
553 throw new ArgumentException ("tasks is empty", "tasks");
556 foreach (var t in tasks)
557 result &= t.Wait (millisecondsTimeout, token);
561 public static int WaitAny (params Task[] tasks)
563 return WaitAny (tasks, null, null);
566 static int WaitAny (Task[] tasks, Func<bool> stopFunc, CancellationToken? token)
569 throw new ArgumentNullException ("tasks");
570 if (tasks.Length == 0)
571 throw new ArgumentException ("tasks is empty", "tasks");
574 int indexFirstFinished = -1;
577 foreach (Task t in tasks) {
578 t.ContinueWith (delegate {
579 int indexResult = index;
580 int result = Interlocked.Increment (ref numFinished);
581 // Check if we are the first to have finished
583 indexFirstFinished = indexResult;
588 // One task already finished
589 if (indexFirstFinished != -1)
590 return indexFirstFinished;
592 // All tasks are supposed to use the same TaskManager
593 tasks[0].scheduler.ParticipateUntil (delegate {
594 if (stopFunc != null && stopFunc ())
597 if (token.HasValue && token.Value.IsCancellationRequested)
598 throw new OperationCanceledException ("The CancellationToken has had cancellation requested.");
600 return numFinished >= 1;
603 return indexFirstFinished;
606 public static int WaitAny (Task[] tasks, TimeSpan ts)
608 return WaitAny (tasks, (int)ts.TotalMilliseconds);
611 public static int WaitAny (Task[] tasks, int millisecondsTimeout)
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, null);
623 public static int WaitAny (Task[] tasks, int millisecondsTimeout, CancellationToken token)
625 if (millisecondsTimeout < -1)
626 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
628 if (millisecondsTimeout == -1)
629 return WaitAny (tasks);
631 Watch sw = Watch.StartNew ();
632 return WaitAny (tasks, () => sw.ElapsedMilliseconds > millisecondsTimeout, token);
635 public static int WaitAny (Task[] tasks, CancellationToken token)
637 return WaitAny (tasks, null, token);
642 public void Dispose ()
647 protected virtual void Dispose (bool disposeManagedRes)
649 // Set action to null so that the GC can collect the delegate and thus
650 // any big object references that the user might have captured in a anonymous method
651 if (disposeManagedRes) {
660 public static TaskFactory Factory {
662 return defaultFactory;
666 public static Task Current {
672 public CancellationToken CancellationToken {
678 public Exception Exception {
680 exceptionObserved = true;
689 public bool IsCanceled {
691 return status == TaskStatus.Canceled;
695 public bool IsCancellationRequested {
697 return src.IsCancellationRequested;
701 public bool IsCompleted {
703 return status == TaskStatus.RanToCompletion ||
704 status == TaskStatus.Canceled || status == TaskStatus.Faulted;
708 public bool IsFaulted {
710 return status == TaskStatus.Faulted;
720 public TaskCreationOptions CreationOptions {
722 return taskCreationOptions;
726 public TaskStatus Status {
735 public object AsyncState {
741 bool IAsyncResult.CompletedSynchronously {
747 WaitHandle IAsyncResult.AsyncWaitHandle {