3 // Copyright (c) 2008 Jérémie "Garuma" Laval
4 // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
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
29 using System.Threading;
30 using System.Collections.Concurrent;
32 namespace System.Threading.Tasks
34 [System.Diagnostics.DebuggerDisplay ("Id = {Id}, Status = {Status}")]
35 [System.Diagnostics.DebuggerTypeProxy (typeof (TaskDebuggerView))]
36 public class Task : IDisposable, IAsyncResult
38 // With this attribute each thread has its own value so that it's correct for our Schedule code
39 // and for Parent property.
43 static Action<Task> childWorkAdder;
48 static TaskFactory defaultFactory = new TaskFactory ();
50 CountdownEvent childTasks = new CountdownEvent (1);
53 TaskCreationOptions taskCreationOptions;
55 TaskScheduler scheduler;
57 ManualResetEventSlim schedWait = new ManualResetEventSlim (false);
59 volatile AggregateException exception;
60 volatile bool exceptionObserved;
61 ConcurrentQueue<AggregateException> childExceptions;
65 Action<object> action;
68 AtomicBooleanValue executing;
70 ConcurrentQueue<EventHandler> completed;
72 CancellationToken token;
74 const TaskCreationOptions MaxTaskCreationOptions =
75 TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent;
77 public Task (Action action) : this (action, TaskCreationOptions.None)
82 public Task (Action action, TaskCreationOptions creationOptions) : this (action, CancellationToken.None, creationOptions)
87 public Task (Action action, CancellationToken cancellationToken) : this (action, cancellationToken, TaskCreationOptions.None)
92 public Task (Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
93 : this (null, null, cancellationToken, creationOptions, current)
96 throw new ArgumentNullException ("action");
97 if (creationOptions > MaxTaskCreationOptions || creationOptions < TaskCreationOptions.None)
98 throw new ArgumentOutOfRangeException ("creationOptions");
99 this.simpleAction = action;
102 public Task (Action<object> action, object state) : this (action, state, TaskCreationOptions.None)
106 public Task (Action<object> action, object state, TaskCreationOptions creationOptions)
107 : this (action, state, CancellationToken.None, creationOptions)
111 public Task (Action<object> action, object state, CancellationToken cancellationToken)
112 : this (action, state, cancellationToken, TaskCreationOptions.None)
116 public Task (Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
117 : this (action, state, cancellationToken, creationOptions, current)
120 throw new ArgumentNullException ("action");
121 if (creationOptions > MaxTaskCreationOptions || creationOptions < TaskCreationOptions.None)
122 throw new ArgumentOutOfRangeException ("creationOptions");
125 internal Task (Action<object> action,
127 CancellationToken cancellationToken,
128 TaskCreationOptions creationOptions,
131 this.taskCreationOptions = creationOptions;
132 this.action = action;
134 this.taskId = Interlocked.Increment (ref id);
135 this.status = cancellationToken.IsCancellationRequested ? TaskStatus.Canceled : TaskStatus.Created;
136 this.token = cancellationToken;
137 this.parent = parent;
139 // Process taskCreationOptions
140 if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.AttachedToParent) && parent != null)
146 if (exception != null && !exceptionObserved)
150 bool CheckTaskOptions (TaskCreationOptions opt, TaskCreationOptions member)
152 return (opt & member) == member;
158 Start (TaskScheduler.Current);
161 public void Start (TaskScheduler scheduler)
163 if (status >= TaskStatus.WaitingToRun)
164 throw new InvalidOperationException ("The Task is not in a valid state to be started.");
165 SetupScheduler (scheduler);
169 internal void SetupScheduler (TaskScheduler scheduler)
171 this.scheduler = scheduler;
172 status = TaskStatus.WaitingForActivation;
176 public void RunSynchronously ()
178 RunSynchronously (TaskScheduler.Current);
181 public void RunSynchronously (TaskScheduler scheduler)
183 if (scheduler == null)
184 throw new ArgumentNullException ("scheduler");
186 if (Status > TaskStatus.WaitingForActivation)
187 throw new InvalidOperationException ("The task is not in a valid state to be started");
189 SetupScheduler (scheduler);
190 var saveStatus = status;
191 status = TaskStatus.WaitingToRun;
194 if (scheduler.RunInline (this))
196 } catch (Exception inner) {
197 throw new TaskSchedulerException (inner);
207 public Task ContinueWith (Action<Task> continuationAction)
209 return ContinueWith (continuationAction, TaskContinuationOptions.None);
212 public Task ContinueWith (Action<Task> continuationAction, TaskContinuationOptions continuationOptions)
214 return ContinueWith (continuationAction, CancellationToken.None, continuationOptions, TaskScheduler.Current);
217 public Task ContinueWith (Action<Task> continuationAction, CancellationToken cancellationToken)
219 return ContinueWith (continuationAction, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current);
222 public Task ContinueWith (Action<Task> continuationAction, TaskScheduler scheduler)
224 return ContinueWith (continuationAction, CancellationToken.None, TaskContinuationOptions.None, scheduler);
227 public Task ContinueWith (Action<Task> continuationAction, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
229 Task continuation = new Task ((o) => continuationAction ((Task)o),
232 GetCreationOptions (continuationOptions),
234 ContinueWithCore (continuation, continuationOptions, scheduler);
239 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction)
241 return ContinueWith<TResult> (continuationFunction, TaskContinuationOptions.None);
244 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, TaskContinuationOptions continuationOptions)
246 return ContinueWith<TResult> (continuationFunction, CancellationToken.None, continuationOptions, TaskScheduler.Current);
249 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, CancellationToken cancellationToken)
251 return ContinueWith<TResult> (continuationFunction, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current);
254 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, TaskScheduler scheduler)
256 return ContinueWith<TResult> (continuationFunction, CancellationToken.None, TaskContinuationOptions.None, scheduler);
259 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, CancellationToken cancellationToken,
260 TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
262 if (continuationFunction == null)
263 throw new ArgumentNullException ("continuationFunction");
264 if (scheduler == null)
265 throw new ArgumentNullException ("scheduler");
267 Task<TResult> t = new Task<TResult> ((o) => continuationFunction ((Task)o),
270 GetCreationOptions (continuationOptions),
273 ContinueWithCore (t, continuationOptions, scheduler);
278 internal void ContinueWithCore (Task continuation, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
280 ContinueWithCore (continuation, continuationOptions, scheduler, null);
283 internal void ContinueWithCore (Task continuation, TaskContinuationOptions kind,
284 TaskScheduler scheduler, Func<bool> predicate)
286 // Already set the scheduler so that user can call Wait and that sort of stuff
287 continuation.scheduler = scheduler;
288 continuation.schedWait.Set ();
289 continuation.status = TaskStatus.WaitingForActivation;
291 AtomicBoolean launched = new AtomicBoolean ();
292 EventHandler action = delegate (object sender, EventArgs e) {
293 if (launched.TryRelaxedSet ()) {
294 if (predicate != null && !predicate ())
297 if (!ContinuationStatusCheck (kind)) {
298 continuation.CancelReal ();
299 continuation.Dispose ();
304 CheckAndSchedule (continuation, kind, scheduler, sender == null);
309 action (null, EventArgs.Empty);
313 if (completed == null)
314 Interlocked.CompareExchange (ref completed, new ConcurrentQueue<EventHandler> (), null);
315 completed.Enqueue (action);
317 // Retry in case completion was achieved but event adding was too late
319 action (null, EventArgs.Empty);
323 bool ContinuationStatusCheck (TaskContinuationOptions kind)
325 if (kind == TaskContinuationOptions.None)
328 int kindCode = (int)kind;
330 if (kindCode >= ((int)TaskContinuationOptions.NotOnRanToCompletion)) {
331 // Remove other options
332 kind &= ~(TaskContinuationOptions.PreferFairness
333 | TaskContinuationOptions.LongRunning
334 | TaskContinuationOptions.AttachedToParent
335 | TaskContinuationOptions.ExecuteSynchronously);
337 if (status == TaskStatus.Canceled) {
338 if (kind == TaskContinuationOptions.NotOnCanceled)
340 if (kind == TaskContinuationOptions.OnlyOnFaulted)
342 if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
344 } else if (status == TaskStatus.Faulted) {
345 if (kind == TaskContinuationOptions.NotOnFaulted)
347 if (kind == TaskContinuationOptions.OnlyOnCanceled)
349 if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
351 } else if (status == TaskStatus.RanToCompletion) {
352 if (kind == TaskContinuationOptions.NotOnRanToCompletion)
354 if (kind == TaskContinuationOptions.OnlyOnFaulted)
356 if (kind == TaskContinuationOptions.OnlyOnCanceled)
364 void CheckAndSchedule (Task continuation, TaskContinuationOptions options, TaskScheduler scheduler, bool fromCaller)
366 if ((options & TaskContinuationOptions.ExecuteSynchronously) > 0)
367 continuation.RunSynchronously (scheduler);
369 continuation.Start (scheduler);
372 internal TaskCreationOptions GetCreationOptions (TaskContinuationOptions kind)
374 TaskCreationOptions options = TaskCreationOptions.None;
375 if ((kind & TaskContinuationOptions.AttachedToParent) > 0)
376 options |= TaskCreationOptions.AttachedToParent;
377 if ((kind & TaskContinuationOptions.PreferFairness) > 0)
378 options |= TaskCreationOptions.PreferFairness;
379 if ((kind & TaskContinuationOptions.LongRunning) > 0)
380 options |= TaskCreationOptions.LongRunning;
386 #region Internal and protected thingies
387 internal void Schedule ()
389 status = TaskStatus.WaitingToRun;
391 // If worker is null it means it is a local one, revert to the old behavior
392 // If TaskScheduler.Current is not being used, the scheduler was explicitly provided, so we must use that
393 if (scheduler != TaskScheduler.Current || childWorkAdder == null || CheckTaskOptions (taskCreationOptions, TaskCreationOptions.PreferFairness)) {
394 scheduler.QueueTask (this);
396 /* Like the semantic of the ABP paper describe it, we add ourselves to the bottom
397 * of our Parent Task's ThreadWorker deque. It's ok to do that since we are in
398 * the correct Thread during the creation
400 childWorkAdder (this);
406 /* Allow scheduler to break fairness of deque ordering without
407 * breaking its semantic (the task can be executed twice but the
408 * second time it will return immediately
410 if (!executing.TryRelaxedSet ())
414 TaskScheduler.Current = scheduler;
416 if (!token.IsCancellationRequested) {
418 status = TaskStatus.Running;
422 } catch (OperationCanceledException oce) {
423 if (token != CancellationToken.None && oce.CancellationToken == token)
426 HandleGenericException (oce);
427 } catch (Exception e) {
428 HandleGenericException (e);
437 internal void Execute (Action<Task> childAdder)
439 childWorkAdder = childAdder;
443 internal void AddChild ()
445 childTasks.AddCount ();
448 internal void ChildCompleted (AggregateException childEx)
450 if (childEx != null) {
451 if (childExceptions == null)
452 Interlocked.CompareExchange (ref childExceptions, new ConcurrentQueue<AggregateException> (), null);
453 childExceptions.Enqueue (childEx);
456 if (childTasks.Signal () && status == TaskStatus.WaitingForChildrenToComplete) {
457 status = TaskStatus.RanToCompletion;
458 ProcessChildExceptions ();
459 ProcessCompleteDelegates ();
463 internal virtual void InnerInvoke ()
465 if (action == null && simpleAction != null)
467 else if (action != null)
469 // Set action to null so that the GC can collect the delegate and thus
470 // any big object references that the user might have captured in an anonymous method
476 internal void Finish ()
478 // If there wasn't any child created in the task we set the CountdownEvent
479 childTasks.Signal ();
481 // Don't override Canceled or Faulted
482 if (status == TaskStatus.Running) {
483 if (childTasks.IsSet)
484 status = TaskStatus.RanToCompletion;
486 status = TaskStatus.WaitingForChildrenToComplete;
489 if (status != TaskStatus.WaitingForChildrenToComplete)
490 ProcessCompleteDelegates ();
492 // Reset the current thingies
494 TaskScheduler.Current = null;
496 // Tell parent that we are finished
497 if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.AttachedToParent) && parent != null) {
498 parent.ChildCompleted (this.Exception);
502 void ProcessCompleteDelegates ()
504 if (completed == null)
507 EventHandler handler;
508 while (completed.TryDequeue (out handler))
509 handler (this, EventArgs.Empty);
512 void ProcessChildExceptions ()
514 if (childExceptions == null)
517 if (exception == null)
518 exception = new AggregateException ();
520 AggregateException childEx;
521 while (childExceptions.TryDequeue (out childEx))
522 exception.AddChildException (childEx);
526 #region Cancel and Wait related method
528 internal void CancelReal ()
530 status = TaskStatus.Canceled;
533 internal void HandleGenericException (Exception e)
535 HandleGenericException (new AggregateException (e));
538 internal void HandleGenericException (AggregateException e)
541 Thread.MemoryBarrier ();
542 status = TaskStatus.Faulted;
543 if (scheduler != null && scheduler.FireUnobservedEvent (exception).Observed)
544 exceptionObserved = true;
549 if (scheduler == null)
553 scheduler.ParticipateUntil (this);
554 if (exception != null)
557 throw new AggregateException (new TaskCanceledException (this));
560 public void Wait (CancellationToken cancellationToken)
562 Wait (-1, cancellationToken);
565 public bool Wait (TimeSpan timeout)
567 return Wait (CheckTimeout (timeout), CancellationToken.None);
570 public bool Wait (int millisecondsTimeout)
572 return Wait (millisecondsTimeout, CancellationToken.None);
575 public bool Wait (int millisecondsTimeout, CancellationToken cancellationToken)
577 if (millisecondsTimeout < -1)
578 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
580 if (millisecondsTimeout == -1 && token == CancellationToken.None) {
585 Watch watch = Watch.StartNew ();
587 if (scheduler == null) {
588 schedWait.Wait (millisecondsTimeout, cancellationToken);
589 millisecondsTimeout = ComputeTimeout (millisecondsTimeout, watch);
592 ManualResetEventSlim predicateEvt = new ManualResetEventSlim (false);
593 if (cancellationToken != CancellationToken.None) {
594 cancellationToken.Register (predicateEvt.Set);
595 cancellationToken.ThrowIfCancellationRequested ();
598 bool result = scheduler.ParticipateUntil (this, predicateEvt, millisecondsTimeout);
600 if (exception != null)
603 throw new AggregateException (new TaskCanceledException (this));
608 public static void WaitAll (params Task[] tasks)
611 throw new ArgumentNullException ("tasks");
613 foreach (var t in tasks) {
615 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
620 public static void WaitAll (Task[] tasks, CancellationToken cancellationToken)
623 throw new ArgumentNullException ("tasks");
625 foreach (var t in tasks) {
627 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
629 t.Wait (cancellationToken);
633 public static bool WaitAll (Task[] tasks, TimeSpan timeout)
636 throw new ArgumentNullException ("tasks");
639 foreach (var t in tasks) {
641 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
643 result &= t.Wait (timeout);
648 public static bool WaitAll (Task[] tasks, int millisecondsTimeout)
651 throw new ArgumentNullException ("tasks");
654 foreach (var t in tasks) {
656 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
658 result &= t.Wait (millisecondsTimeout);
663 public static bool WaitAll (Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
666 throw new ArgumentNullException ("tasks");
669 foreach (var t in tasks) {
671 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
673 result &= t.Wait (millisecondsTimeout, cancellationToken);
678 public static int WaitAny (params Task[] tasks)
680 return WaitAny (tasks, -1, CancellationToken.None);
683 public static int WaitAny (Task[] tasks, TimeSpan timeout)
685 return WaitAny (tasks, CheckTimeout (timeout));
688 public static int WaitAny (Task[] tasks, int millisecondsTimeout)
690 if (millisecondsTimeout < -1)
691 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
693 if (millisecondsTimeout == -1)
694 return WaitAny (tasks);
696 return WaitAny (tasks, millisecondsTimeout, CancellationToken.None);
699 public static int WaitAny (Task[] tasks, CancellationToken cancellationToken)
701 return WaitAny (tasks, -1, cancellationToken);
704 public static int WaitAny (Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
707 throw new ArgumentNullException ("tasks");
708 if (tasks.Length == 0)
709 throw new ArgumentException ("tasks is empty", "tasks");
710 if (tasks.Length == 1) {
711 tasks[0].Wait (millisecondsTimeout, cancellationToken);
716 int indexFirstFinished = -1;
718 TaskScheduler sched = null;
720 Watch watch = Watch.StartNew ();
721 ManualResetEventSlim predicateEvt = new ManualResetEventSlim (false);
723 foreach (Task t in tasks) {
724 int indexResult = index++;
725 t.ContinueWith (delegate {
726 if (numFinished >= 1)
728 int result = Interlocked.Increment (ref numFinished);
730 // Check if we are the first to have finished
732 indexFirstFinished = indexResult;
736 }, TaskContinuationOptions.ExecuteSynchronously);
738 if (sched == null && t.scheduler != null) {
744 // If none of task have a scheduler we are forced to wait for at least one to start
746 var handles = Array.ConvertAll (tasks, t => t.schedWait.WaitHandle);
748 if ((shandle = WaitHandle.WaitAny (handles, millisecondsTimeout)) == WaitHandle.WaitTimeout)
750 sched = tasks[shandle].scheduler;
751 task = tasks[shandle];
752 millisecondsTimeout = ComputeTimeout (millisecondsTimeout, watch);
755 // One task already finished
756 if (indexFirstFinished != -1)
757 return indexFirstFinished;
759 if (cancellationToken != CancellationToken.None) {
760 cancellationToken.Register (predicateEvt.Set);
761 cancellationToken.ThrowIfCancellationRequested ();
764 sched.ParticipateUntil (task, predicateEvt, millisecondsTimeout);
766 // Index update is still not done
767 if (indexFirstFinished == -1) {
768 SpinWait wait = new SpinWait ();
769 while (indexFirstFinished == -1)
773 return indexFirstFinished;
776 static int CheckTimeout (TimeSpan timeout)
779 return checked ((int)timeout.TotalMilliseconds);
780 } catch (System.OverflowException) {
781 throw new ArgumentOutOfRangeException ("timeout");
785 static int ComputeTimeout (int millisecondsTimeout, Watch watch)
787 return millisecondsTimeout == -1 ? -1 : (int)Math.Max (watch.ElapsedMilliseconds - millisecondsTimeout, 1);
793 public void Dispose ()
798 protected virtual void Dispose (bool disposing)
801 throw new InvalidOperationException ("A task may only be disposed if it is in a completion state");
803 // Set action to null so that the GC can collect the delegate and thus
804 // any big object references that the user might have captured in a anonymous method
813 public static TaskFactory Factory {
815 return defaultFactory;
819 public static int? CurrentId {
822 return t == null ? (int?)null : t.Id;
826 public AggregateException Exception {
828 exceptionObserved = true;
837 public bool IsCanceled {
839 return status == TaskStatus.Canceled;
843 public bool IsCompleted {
845 return status == TaskStatus.RanToCompletion ||
846 status == TaskStatus.Canceled || status == TaskStatus.Faulted;
850 public bool IsFaulted {
852 return status == TaskStatus.Faulted;
856 public TaskCreationOptions CreationOptions {
858 return taskCreationOptions;
862 public TaskStatus Status {
871 public object AsyncState {
877 bool IAsyncResult.CompletedSynchronously {
883 WaitHandle IAsyncResult.AsyncWaitHandle {
895 internal Task Parent {
901 internal string DisplayActionMethod {
903 Delegate d = simpleAction ?? (Delegate) action;
904 return d == null ? "<none>" : d.Method.ToString ();