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 {