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 = {DisplayActionMethod}")]
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;
60 ConcurrentQueue<AggregateException> childExceptions;
64 Action<object> action;
67 AtomicBooleanValue executing;
69 ConcurrentQueue<EventHandler> completed;
71 CancellationToken token;
73 public Task (Action action) : this (action, TaskCreationOptions.None)
78 public Task (Action action, TaskCreationOptions creationOptions) : this (action, CancellationToken.None, creationOptions)
83 public Task (Action action, CancellationToken cancellationToken) : this (action, cancellationToken, TaskCreationOptions.None)
88 public Task (Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
89 : this (null, null, cancellationToken, creationOptions)
91 this.simpleAction = action;
94 public Task (Action<object> action, object state) : this (action, state, TaskCreationOptions.None)
98 public Task (Action<object> action, object state, TaskCreationOptions creationOptions)
99 : this (action, state, CancellationToken.None, creationOptions)
103 public Task (Action<object> action, object state, CancellationToken cancellationToken)
104 : this (action, state, cancellationToken, TaskCreationOptions.None)
108 public Task (Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
109 : this (action, state, cancellationToken, creationOptions, current)
114 internal Task (Action<object> action,
116 CancellationToken cancellationToken,
117 TaskCreationOptions creationOptions,
120 this.taskCreationOptions = creationOptions;
121 this.action = action;
123 this.taskId = Interlocked.Increment (ref id);
124 this.status = TaskStatus.Created;
125 this.token = cancellationToken;
126 this.parent = parent;
128 // Process taskCreationOptions
129 if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.AttachedToParent) && parent != null)
135 if (exception != null && !exceptionObserved)
139 bool CheckTaskOptions (TaskCreationOptions opt, TaskCreationOptions member)
141 return (opt & member) == member;
147 Start (TaskScheduler.Current);
150 public void Start (TaskScheduler scheduler)
152 SetupScheduler (scheduler);
156 internal void SetupScheduler (TaskScheduler scheduler)
158 this.scheduler = scheduler;
159 status = TaskStatus.WaitingForActivation;
163 public void RunSynchronously ()
165 RunSynchronously (TaskScheduler.Current);
168 public void RunSynchronously (TaskScheduler scheduler)
170 if (Status > TaskStatus.WaitingForActivation)
171 throw new InvalidOperationException ("The task is not in a valid state to be started");
173 SetupScheduler (scheduler);
174 status = TaskStatus.WaitingToRun;
176 if (scheduler.RunInline (this))
185 public Task ContinueWith (Action<Task> continuationAction)
187 return ContinueWith (continuationAction, TaskContinuationOptions.None);
190 public Task ContinueWith (Action<Task> continuationAction, TaskContinuationOptions continuationOptions)
192 return ContinueWith (continuationAction, CancellationToken.None, continuationOptions, TaskScheduler.Current);
195 public Task ContinueWith (Action<Task> continuationAction, CancellationToken cancellationToken)
197 return ContinueWith (continuationAction, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current);
200 public Task ContinueWith (Action<Task> continuationAction, TaskScheduler scheduler)
202 return ContinueWith (continuationAction, CancellationToken.None, TaskContinuationOptions.None, scheduler);
205 public Task ContinueWith (Action<Task> continuationAction, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
207 Task continuation = new Task ((o) => continuationAction ((Task)o),
210 GetCreationOptions (continuationOptions),
212 ContinueWithCore (continuation, continuationOptions, scheduler);
217 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction)
219 return ContinueWith<TResult> (continuationFunction, TaskContinuationOptions.None);
222 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, TaskContinuationOptions continuationOptions)
224 return ContinueWith<TResult> (continuationFunction, CancellationToken.None, continuationOptions, TaskScheduler.Current);
227 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, CancellationToken cancellationToken)
229 return ContinueWith<TResult> (continuationFunction, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current);
232 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, TaskScheduler scheduler)
234 return ContinueWith<TResult> (continuationFunction, CancellationToken.None, TaskContinuationOptions.None, scheduler);
237 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, CancellationToken cancellationToken,
238 TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
240 if (continuationFunction == null)
241 throw new ArgumentNullException ("continuationFunction");
242 if (scheduler == null)
243 throw new ArgumentNullException ("scheduler");
245 Task<TResult> t = new Task<TResult> ((o) => continuationFunction ((Task)o),
248 GetCreationOptions (continuationOptions),
251 ContinueWithCore (t, continuationOptions, scheduler);
256 internal void ContinueWithCore (Task continuation, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
258 ContinueWithCore (continuation, continuationOptions, scheduler, () => true);
261 internal void ContinueWithCore (Task continuation, TaskContinuationOptions kind,
262 TaskScheduler scheduler, Func<bool> predicate)
264 // Already set the scheduler so that user can call Wait and that sort of stuff
265 continuation.scheduler = scheduler;
266 continuation.schedWait.Set ();
267 continuation.status = TaskStatus.WaitingForActivation;
269 AtomicBoolean launched = new AtomicBoolean ();
270 EventHandler action = delegate (object sender, EventArgs e) {
271 if (launched.TryRelaxedSet ()) {
275 if (!ContinuationStatusCheck (kind)) {
276 continuation.CancelReal ();
277 continuation.Dispose ();
282 CheckAndSchedule (continuation, kind, scheduler, sender == null);
287 action (null, EventArgs.Empty);
291 if (completed == null)
292 Interlocked.CompareExchange (ref completed, new ConcurrentQueue<EventHandler> (), null);
293 completed.Enqueue (action);
295 // Retry in case completion was achieved but event adding was too late
297 action (null, EventArgs.Empty);
301 bool ContinuationStatusCheck (TaskContinuationOptions kind)
303 if (kind == TaskContinuationOptions.None)
306 int kindCode = (int)kind;
308 if (kindCode >= ((int)TaskContinuationOptions.NotOnRanToCompletion)) {
309 // Remove other options
310 kind &= ~(TaskContinuationOptions.PreferFairness
311 | TaskContinuationOptions.LongRunning
312 | TaskContinuationOptions.AttachedToParent
313 | TaskContinuationOptions.ExecuteSynchronously);
315 if (status == TaskStatus.Canceled) {
316 if (kind == TaskContinuationOptions.NotOnCanceled)
318 if (kind == TaskContinuationOptions.OnlyOnFaulted)
320 if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
322 } else if (status == TaskStatus.Faulted) {
323 if (kind == TaskContinuationOptions.NotOnFaulted)
325 if (kind == TaskContinuationOptions.OnlyOnCanceled)
327 if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
329 } else if (status == TaskStatus.RanToCompletion) {
330 if (kind == TaskContinuationOptions.NotOnRanToCompletion)
332 if (kind == TaskContinuationOptions.OnlyOnFaulted)
334 if (kind == TaskContinuationOptions.OnlyOnCanceled)
342 void CheckAndSchedule (Task continuation, TaskContinuationOptions options, TaskScheduler scheduler, bool fromCaller)
344 if ((options & TaskContinuationOptions.ExecuteSynchronously) > 0)
345 continuation.RunSynchronously (scheduler);
347 continuation.Start (scheduler);
350 internal TaskCreationOptions GetCreationOptions (TaskContinuationOptions kind)
352 TaskCreationOptions options = TaskCreationOptions.None;
353 if ((kind & TaskContinuationOptions.AttachedToParent) > 0)
354 options |= TaskCreationOptions.AttachedToParent;
355 if ((kind & TaskContinuationOptions.PreferFairness) > 0)
356 options |= TaskCreationOptions.PreferFairness;
357 if ((kind & TaskContinuationOptions.LongRunning) > 0)
358 options |= TaskCreationOptions.LongRunning;
364 #region Internal and protected thingies
365 internal void Schedule ()
367 status = TaskStatus.WaitingToRun;
369 // If worker is null it means it is a local one, revert to the old behavior
370 // If TaskScheduler.Current is not being used, the scheduler was explicitly provided, so we must use that
371 if (scheduler != TaskScheduler.Current || childWorkAdder == null || CheckTaskOptions (taskCreationOptions, TaskCreationOptions.PreferFairness)) {
372 scheduler.QueueTask (this);
374 /* Like the semantic of the ABP paper describe it, we add ourselves to the bottom
375 * of our Parent Task's ThreadWorker deque. It's ok to do that since we are in
376 * the correct Thread during the creation
378 childWorkAdder (this);
384 /* Allow scheduler to break fairness of deque ordering without
385 * breaking its semantic (the task can be executed twice but the
386 * second time it will return immediately
388 if (!executing.TryRelaxedSet ())
392 TaskScheduler.Current = scheduler;
394 if (!token.IsCancellationRequested) {
396 status = TaskStatus.Running;
400 } catch (OperationCanceledException oce) {
401 if (oce.CancellationToken == token)
404 HandleGenericException (oce);
405 } catch (Exception e) {
406 HandleGenericException (e);
415 internal void Execute (Action<Task> childAdder)
417 childWorkAdder = childAdder;
421 internal void AddChild ()
423 childTasks.AddCount ();
426 internal void ChildCompleted (AggregateException childEx)
428 if (childEx != null) {
429 if (childExceptions == null)
430 Interlocked.CompareExchange (ref childExceptions, new ConcurrentQueue<AggregateException> (), null);
431 childExceptions.Enqueue (childEx);
434 if (childTasks.Signal () && status == TaskStatus.WaitingForChildrenToComplete) {
435 status = TaskStatus.RanToCompletion;
436 ProcessChildExceptions ();
437 ProcessCompleteDelegates ();
441 internal virtual void InnerInvoke ()
443 if (action == null && simpleAction != null)
445 else if (action != null)
447 // Set action to null so that the GC can collect the delegate and thus
448 // any big object references that the user might have captured in an anonymous method
454 internal void Finish ()
456 // If there wasn't any child created in the task we set the CountdownEvent
457 childTasks.Signal ();
459 // Don't override Canceled or Faulted
460 if (status == TaskStatus.Running) {
461 if (childTasks.IsSet)
462 status = TaskStatus.RanToCompletion;
464 status = TaskStatus.WaitingForChildrenToComplete;
467 if (status != TaskStatus.WaitingForChildrenToComplete)
468 ProcessCompleteDelegates ();
470 // Reset the current thingies
472 TaskScheduler.Current = null;
474 // Tell parent that we are finished
475 if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.AttachedToParent) && parent != null) {
476 parent.ChildCompleted (this.Exception);
480 void ProcessCompleteDelegates ()
482 if (completed == null)
485 EventHandler handler;
486 while (completed.TryDequeue (out handler))
487 handler (this, EventArgs.Empty);
490 void ProcessChildExceptions ()
492 if (childExceptions == null)
495 if (exception == null)
496 exception = new AggregateException ();
498 AggregateException childEx;
499 while (childExceptions.TryDequeue (out childEx))
500 exception.AddChildException (childEx);
504 #region Cancel and Wait related method
506 internal void CancelReal ()
508 status = TaskStatus.Canceled;
511 internal void HandleGenericException (Exception e)
513 HandleGenericException (new AggregateException (e));
516 internal void HandleGenericException (AggregateException e)
519 Thread.MemoryBarrier ();
520 status = TaskStatus.Faulted;
521 if (scheduler != null && scheduler.FireUnobservedEvent (exception).Observed)
522 exceptionObserved = true;
527 if (scheduler == null)
531 scheduler.ParticipateUntil (this);
532 if (exception != null)
535 throw new AggregateException (new TaskCanceledException (this));
538 public void Wait (CancellationToken cancellationToken)
540 Wait (-1, cancellationToken);
543 public bool Wait (TimeSpan timeout)
545 return Wait (CheckTimeout (timeout), CancellationToken.None);
548 public bool Wait (int millisecondsTimeout)
550 return Wait (millisecondsTimeout, CancellationToken.None);
553 public bool Wait (int millisecondsTimeout, CancellationToken cancellationToken)
555 if (millisecondsTimeout < -1)
556 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
558 if (millisecondsTimeout == -1 && token == CancellationToken.None) {
563 Watch watch = Watch.StartNew ();
565 if (scheduler == null) {
566 schedWait.Wait (millisecondsTimeout, cancellationToken);
567 millisecondsTimeout = ComputeTimeout (millisecondsTimeout, watch);
570 ManualResetEventSlim predicateEvt = new ManualResetEventSlim (false);
571 if (cancellationToken != CancellationToken.None) {
572 cancellationToken.Register (predicateEvt.Set);
573 cancellationToken.ThrowIfCancellationRequested ();
576 bool result = scheduler.ParticipateUntil (this, predicateEvt, millisecondsTimeout);
578 if (exception != null)
581 throw new AggregateException (new TaskCanceledException (this));
586 public static void WaitAll (params Task[] tasks)
589 throw new ArgumentNullException ("tasks");
591 foreach (var t in tasks) {
593 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
598 public static void WaitAll (Task[] tasks, CancellationToken cancellationToken)
601 throw new ArgumentNullException ("tasks");
603 foreach (var t in tasks) {
605 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
607 t.Wait (cancellationToken);
611 public static bool WaitAll (Task[] tasks, TimeSpan timeout)
614 throw new ArgumentNullException ("tasks");
617 foreach (var t in tasks) {
619 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
621 result &= t.Wait (timeout);
626 public static bool WaitAll (Task[] tasks, int millisecondsTimeout)
629 throw new ArgumentNullException ("tasks");
632 foreach (var t in tasks) {
634 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
636 result &= t.Wait (millisecondsTimeout);
641 public static bool WaitAll (Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
644 throw new ArgumentNullException ("tasks");
647 foreach (var t in tasks) {
649 throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
651 result &= t.Wait (millisecondsTimeout, cancellationToken);
656 public static int WaitAny (params Task[] tasks)
658 return WaitAny (tasks, -1, CancellationToken.None);
661 public static int WaitAny (Task[] tasks, TimeSpan timeout)
663 return WaitAny (tasks, CheckTimeout (timeout));
666 public static int WaitAny (Task[] tasks, int millisecondsTimeout)
668 if (millisecondsTimeout < -1)
669 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
671 if (millisecondsTimeout == -1)
672 return WaitAny (tasks);
674 return WaitAny (tasks, millisecondsTimeout, CancellationToken.None);
677 public static int WaitAny (Task[] tasks, CancellationToken cancellationToken)
679 return WaitAny (tasks, -1, cancellationToken);
682 public static int WaitAny (Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
685 throw new ArgumentNullException ("tasks");
686 if (tasks.Length == 0)
687 throw new ArgumentException ("tasks is empty", "tasks");
688 if (tasks.Length == 1) {
689 tasks[0].Wait (millisecondsTimeout, cancellationToken);
694 int indexFirstFinished = -1;
696 TaskScheduler sched = null;
698 Watch watch = Watch.StartNew ();
699 ManualResetEventSlim predicateEvt = new ManualResetEventSlim (false);
701 foreach (Task t in tasks) {
702 int indexResult = index++;
703 t.ContinueWith (delegate {
704 if (numFinished >= 1)
706 int result = Interlocked.Increment (ref numFinished);
708 // Check if we are the first to have finished
710 indexFirstFinished = indexResult;
714 }, TaskContinuationOptions.ExecuteSynchronously);
716 if (sched == null && t.scheduler != null) {
722 // If none of task have a scheduler we are forced to wait for at least one to start
724 var handles = Array.ConvertAll (tasks, t => t.schedWait.WaitHandle);
726 if ((shandle = WaitHandle.WaitAny (handles, millisecondsTimeout)) == WaitHandle.WaitTimeout)
728 sched = tasks[shandle].scheduler;
729 task = tasks[shandle];
730 millisecondsTimeout = ComputeTimeout (millisecondsTimeout, watch);
733 // One task already finished
734 if (indexFirstFinished != -1)
735 return indexFirstFinished;
737 if (cancellationToken != CancellationToken.None) {
738 cancellationToken.Register (predicateEvt.Set);
739 cancellationToken.ThrowIfCancellationRequested ();
742 sched.ParticipateUntil (task, predicateEvt, millisecondsTimeout);
744 // Index update is still not done
745 if (indexFirstFinished == -1) {
746 SpinWait wait = new SpinWait ();
747 while (indexFirstFinished == -1)
751 return indexFirstFinished;
754 static int CheckTimeout (TimeSpan timeout)
757 return checked ((int)timeout.TotalMilliseconds);
758 } catch (System.OverflowException) {
759 throw new ArgumentOutOfRangeException ("timeout");
763 static int ComputeTimeout (int millisecondsTimeout, Watch watch)
765 return millisecondsTimeout == -1 ? -1 : (int)Math.Max (watch.ElapsedMilliseconds - millisecondsTimeout, 1);
771 public void Dispose ()
776 protected virtual void Dispose (bool disposing)
778 // Set action to null so that the GC can collect the delegate and thus
779 // any big object references that the user might have captured in a anonymous method
788 public static TaskFactory Factory {
790 return defaultFactory;
794 public static int? CurrentId {
797 return t == null ? (int?)null : t.Id;
801 public AggregateException Exception {
803 exceptionObserved = true;
812 public bool IsCanceled {
814 return status == TaskStatus.Canceled;
818 public bool IsCompleted {
820 return status == TaskStatus.RanToCompletion ||
821 status == TaskStatus.Canceled || status == TaskStatus.Faulted;
825 public bool IsFaulted {
827 return status == TaskStatus.Faulted;
831 public TaskCreationOptions CreationOptions {
833 return taskCreationOptions;
837 public TaskStatus Status {
846 public object AsyncState {
852 bool IAsyncResult.CompletedSynchronously {
858 WaitHandle IAsyncResult.AsyncWaitHandle {
870 internal Task Parent {
876 string DisplayActionMethod {
878 Delegate d = simpleAction ?? (Delegate) action;
879 return d == null ? "<none>" : d.Method.ToString ();