Throw when a non-finished task is started
[mono.git] / mcs / class / corlib / System.Threading.Tasks / Task.cs
1 // Task.cs
2 //
3 // Copyright (c) 2008 Jérémie "Garuma" Laval
4 //
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:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
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
21 // THE SOFTWARE.
22 //
23 //
24
25 #if NET_4_0 || MOBILE
26
27 using System;
28 using System.Threading;
29 using System.Collections.Concurrent;
30
31 namespace System.Threading.Tasks
32 {
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
36         {
37                 // With this attribute each thread has its own value so that it's correct for our Schedule code
38                 // and for Parent property.
39                 [System.ThreadStatic]
40                 static Task         current;
41                 [System.ThreadStatic]
42                 static Action<Task> childWorkAdder;
43                 
44                 Task parent;
45                 
46                 static int          id = -1;
47                 static TaskFactory  defaultFactory = new TaskFactory ();
48                 
49                 CountdownEvent childTasks = new CountdownEvent (1);
50                 
51                 int                 taskId;
52                 TaskCreationOptions taskCreationOptions;
53                 
54                 TaskScheduler       scheduler;
55
56                 ManualResetEventSlim schedWait = new ManualResetEventSlim (false);
57                 
58                 volatile AggregateException  exception;
59                 volatile bool                exceptionObserved;
60                 ConcurrentQueue<AggregateException> childExceptions;
61
62                 TaskStatus          status;
63                 
64                 Action<object> action;
65                 Action         simpleAction;
66                 object         state;
67                 AtomicBooleanValue executing;
68
69                 ConcurrentQueue<EventHandler> completed;
70
71                 CancellationToken token;
72
73                 const TaskCreationOptions MaxTaskCreationOptions =
74                         TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent;
75
76                 public Task (Action action) : this (action, TaskCreationOptions.None)
77                 {
78                         
79                 }
80                 
81                 public Task (Action action, TaskCreationOptions creationOptions) : this (action, CancellationToken.None, creationOptions)
82                 {
83                         
84                 }
85                 
86                 public Task (Action action, CancellationToken cancellationToken) : this (action, cancellationToken, TaskCreationOptions.None)
87                 {
88                         
89                 }
90                 
91                 public Task (Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
92                         : this (null, null, cancellationToken, creationOptions, current)
93                 {
94                         if (action == null)
95                                 throw new ArgumentNullException ("action");
96                         if (creationOptions > MaxTaskCreationOptions || creationOptions < TaskCreationOptions.None)
97                                 throw new ArgumentOutOfRangeException ("creationOptions");
98                         this.simpleAction = action;
99                 }
100                 
101                 public Task (Action<object> action, object state) : this (action, state, TaskCreationOptions.None)
102                 {       
103                 }
104                 
105                 public Task (Action<object> action, object state, TaskCreationOptions creationOptions)
106                         : this (action, state, CancellationToken.None, creationOptions)
107                 {
108                 }
109                 
110                 public Task (Action<object> action, object state, CancellationToken cancellationToken)
111                         : this (action, state, cancellationToken, TaskCreationOptions.None)
112                 {       
113                 }
114
115                 public Task (Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
116                         : this (action, state, cancellationToken, creationOptions, current)
117                 {
118                         if (action == null)
119                                 throw new ArgumentNullException ("action");
120                         if (creationOptions > MaxTaskCreationOptions || creationOptions < TaskCreationOptions.None)
121                                 throw new ArgumentOutOfRangeException ("creationOptions");
122                 }
123
124                 internal Task (Action<object> action,
125                                object state,
126                                CancellationToken cancellationToken,
127                                TaskCreationOptions creationOptions,
128                                Task parent)
129                 {
130                         this.taskCreationOptions = creationOptions;
131                         this.action              = action;
132                         this.state               = state;
133                         this.taskId              = Interlocked.Increment (ref id);
134                         this.status              = cancellationToken.IsCancellationRequested ? TaskStatus.Canceled : TaskStatus.Created;
135                         this.token               = cancellationToken;
136                         this.parent              = parent;
137
138                         // Process taskCreationOptions
139                         if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.AttachedToParent) && parent != null)
140                                 parent.AddChild ();
141                 }
142
143                 ~Task ()
144                 {
145                         if (exception != null && !exceptionObserved)
146                                 throw exception;
147                 }
148
149                 bool CheckTaskOptions (TaskCreationOptions opt, TaskCreationOptions member)
150                 {
151                         return (opt & member) == member;
152                 }
153
154                 #region Start
155                 public void Start ()
156                 {
157                         Start (TaskScheduler.Current);
158                 }
159                 
160                 public void Start (TaskScheduler scheduler)
161                 {
162                         if (status >= TaskStatus.WaitingToRun)
163                                 throw new InvalidOperationException ("The Task is not in a valid state to be started.");
164                         SetupScheduler (scheduler);
165                         Schedule ();
166                 }
167
168                 internal void SetupScheduler (TaskScheduler scheduler)
169                 {
170                         this.scheduler = scheduler;
171                         status = TaskStatus.WaitingForActivation;
172                         schedWait.Set ();
173                 }
174                 
175                 public void RunSynchronously ()
176                 {
177                         RunSynchronously (TaskScheduler.Current);
178                 }
179                 
180                 public void RunSynchronously (TaskScheduler scheduler)
181                 {
182                         if (Status > TaskStatus.WaitingForActivation)
183                                 throw new InvalidOperationException ("The task is not in a valid state to be started");
184
185                         SetupScheduler (scheduler);
186                         status = TaskStatus.WaitingToRun;
187
188                         if (scheduler.RunInline (this))
189                                 return;
190
191                         Start (scheduler);
192                         Wait ();
193                 }
194                 #endregion
195                 
196                 #region ContinueWith
197                 public Task ContinueWith (Action<Task> continuationAction)
198                 {
199                         return ContinueWith (continuationAction, TaskContinuationOptions.None);
200                 }
201                 
202                 public Task ContinueWith (Action<Task> continuationAction, TaskContinuationOptions continuationOptions)
203                 {
204                         return ContinueWith (continuationAction, CancellationToken.None, continuationOptions, TaskScheduler.Current);
205                 }
206                 
207                 public Task ContinueWith (Action<Task> continuationAction, CancellationToken cancellationToken)
208                 {
209                         return ContinueWith (continuationAction, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current);
210                 }
211                 
212                 public Task ContinueWith (Action<Task> continuationAction, TaskScheduler scheduler)
213                 {
214                         return ContinueWith (continuationAction, CancellationToken.None, TaskContinuationOptions.None, scheduler);
215                 }
216                 
217                 public Task ContinueWith (Action<Task> continuationAction, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
218                 {
219                         Task continuation = new Task ((o) => continuationAction ((Task)o),
220                                                       this,
221                                                       cancellationToken,
222                                                       GetCreationOptions (continuationOptions),
223                                                       this);
224                         ContinueWithCore (continuation, continuationOptions, scheduler);
225
226                         return continuation;
227                 }
228                 
229                 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction)
230                 {
231                         return ContinueWith<TResult> (continuationFunction, TaskContinuationOptions.None);
232                 }
233                 
234                 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, TaskContinuationOptions continuationOptions)
235                 {
236                         return ContinueWith<TResult> (continuationFunction, CancellationToken.None, continuationOptions, TaskScheduler.Current);
237                 }
238                 
239                 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, CancellationToken cancellationToken)
240                 {
241                         return ContinueWith<TResult> (continuationFunction, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current);
242                 }
243                 
244                 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, TaskScheduler scheduler)
245                 {
246                         return ContinueWith<TResult> (continuationFunction, CancellationToken.None, TaskContinuationOptions.None, scheduler);
247                 }
248                 
249                 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> continuationFunction, CancellationToken cancellationToken,
250                                                             TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
251                 {
252                         if (continuationFunction == null)
253                                 throw new ArgumentNullException ("continuationFunction");
254                         if (scheduler == null)
255                                 throw new ArgumentNullException ("scheduler");
256
257                         Task<TResult> t = new Task<TResult> ((o) => continuationFunction ((Task)o),
258                                                              this,
259                                                              cancellationToken,
260                                                              GetCreationOptions (continuationOptions),
261                                                              this);
262                         
263                         ContinueWithCore (t, continuationOptions, scheduler);
264                         
265                         return t;
266                 }
267                 
268                 internal void ContinueWithCore (Task continuation, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
269                 {
270                         ContinueWithCore (continuation, continuationOptions, scheduler, null);
271                 }
272                 
273                 internal void ContinueWithCore (Task continuation, TaskContinuationOptions kind,
274                                                 TaskScheduler scheduler, Func<bool> predicate)
275                 {
276                         // Already set the scheduler so that user can call Wait and that sort of stuff
277                         continuation.scheduler = scheduler;
278                         continuation.schedWait.Set ();
279                         continuation.status = TaskStatus.WaitingForActivation;
280                         
281                         AtomicBoolean launched = new AtomicBoolean ();
282                         EventHandler action = delegate (object sender, EventArgs e) {
283                                 if (launched.TryRelaxedSet ()) {
284                                         if (predicate != null && !predicate ())
285                                                 return;
286
287                                         if (!ContinuationStatusCheck (kind)) {
288                                                 continuation.CancelReal ();
289                                                 continuation.Dispose ();
290                                                 
291                                                 return;
292                                         }
293                                         
294                                         CheckAndSchedule (continuation, kind, scheduler, sender == null);
295                                 }
296                         };
297                         
298                         if (IsCompleted) {
299                                 action (null, EventArgs.Empty);
300                                 return;
301                         }
302                         
303                         if (completed == null)
304                                 Interlocked.CompareExchange (ref completed, new ConcurrentQueue<EventHandler> (), null);
305                         completed.Enqueue (action);
306                         
307                         // Retry in case completion was achieved but event adding was too late
308                         if (IsCompleted)
309                                 action (null, EventArgs.Empty);
310                 }
311
312                 
313                 bool ContinuationStatusCheck (TaskContinuationOptions kind)
314                 {
315                         if (kind == TaskContinuationOptions.None)
316                                 return true;
317                         
318                         int kindCode = (int)kind;
319                         
320                         if (kindCode >= ((int)TaskContinuationOptions.NotOnRanToCompletion)) {
321                                 // Remove other options
322                                 kind &= ~(TaskContinuationOptions.PreferFairness
323                                           | TaskContinuationOptions.LongRunning
324                                           | TaskContinuationOptions.AttachedToParent
325                                           | TaskContinuationOptions.ExecuteSynchronously);
326
327                                 if (status == TaskStatus.Canceled) {
328                                         if (kind == TaskContinuationOptions.NotOnCanceled)
329                                                 return false;
330                                         if (kind == TaskContinuationOptions.OnlyOnFaulted)
331                                                 return false;
332                                         if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
333                                                 return false;
334                                 } else if (status == TaskStatus.Faulted) {
335                                         if (kind == TaskContinuationOptions.NotOnFaulted)
336                                                 return false;
337                                         if (kind == TaskContinuationOptions.OnlyOnCanceled)
338                                                 return false;
339                                         if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
340                                                 return false;
341                                 } else if (status == TaskStatus.RanToCompletion) {
342                                         if (kind == TaskContinuationOptions.NotOnRanToCompletion)
343                                                 return false;
344                                         if (kind == TaskContinuationOptions.OnlyOnFaulted)
345                                                 return false;
346                                         if (kind == TaskContinuationOptions.OnlyOnCanceled)
347                                                 return false;
348                                 }
349                         }
350                         
351                         return true;
352                 }
353                 
354                 void CheckAndSchedule (Task continuation, TaskContinuationOptions options, TaskScheduler scheduler, bool fromCaller)
355                 {
356                         if ((options & TaskContinuationOptions.ExecuteSynchronously) > 0)
357                                 continuation.RunSynchronously (scheduler);
358                         else
359                                 continuation.Start (scheduler);
360                 }
361                 
362                 internal TaskCreationOptions GetCreationOptions (TaskContinuationOptions kind)
363                 {
364                         TaskCreationOptions options = TaskCreationOptions.None;
365                         if ((kind & TaskContinuationOptions.AttachedToParent) > 0)
366                                 options |= TaskCreationOptions.AttachedToParent;
367                         if ((kind & TaskContinuationOptions.PreferFairness) > 0)
368                                 options |= TaskCreationOptions.PreferFairness;
369                         if ((kind & TaskContinuationOptions.LongRunning) > 0)
370                                 options |= TaskCreationOptions.LongRunning;
371                         
372                         return options;
373                 }
374                 #endregion
375                 
376                 #region Internal and protected thingies
377                 internal void Schedule ()
378                 {
379                         status = TaskStatus.WaitingToRun;
380                         
381                         // If worker is null it means it is a local one, revert to the old behavior
382                         // If TaskScheduler.Current is not being used, the scheduler was explicitly provided, so we must use that
383                         if (scheduler != TaskScheduler.Current || childWorkAdder == null || CheckTaskOptions (taskCreationOptions, TaskCreationOptions.PreferFairness)) {
384                                 scheduler.QueueTask (this);
385                         } else {
386                                 /* Like the semantic of the ABP paper describe it, we add ourselves to the bottom 
387                                  * of our Parent Task's ThreadWorker deque. It's ok to do that since we are in
388                                  * the correct Thread during the creation
389                                  */
390                                 childWorkAdder (this);
391                         }
392                 }
393                 
394                 void ThreadStart ()
395                 {
396                         /* Allow scheduler to break fairness of deque ordering without
397                          * breaking its semantic (the task can be executed twice but the
398                          * second time it will return immediately
399                          */
400                         if (!executing.TryRelaxedSet ())
401                                 return;
402
403                         current = this;
404                         TaskScheduler.Current = scheduler;
405                         
406                         if (!token.IsCancellationRequested) {
407                                 
408                                 status = TaskStatus.Running;
409                                 
410                                 try {
411                                         InnerInvoke ();
412                                 } catch (OperationCanceledException oce) {
413                                         if (oce.CancellationToken == token)
414                                                 CancelReal ();
415                                         else
416                                                 HandleGenericException (oce);
417                                 } catch (Exception e) {
418                                         HandleGenericException (e);
419                                 }
420                         } else {
421                                 CancelReal ();
422                         }
423                         
424                         Finish ();
425                 }
426                 
427                 internal void Execute (Action<Task> childAdder)
428                 {
429                         childWorkAdder = childAdder;
430                         ThreadStart ();
431                 }
432                 
433                 internal void AddChild ()
434                 {
435                         childTasks.AddCount ();
436                 }
437
438                 internal void ChildCompleted (AggregateException childEx)
439                 {
440                         if (childEx != null) {
441                                 if (childExceptions == null)
442                                         Interlocked.CompareExchange (ref childExceptions, new ConcurrentQueue<AggregateException> (), null);
443                                 childExceptions.Enqueue (childEx);
444                         }
445
446                         if (childTasks.Signal () && status == TaskStatus.WaitingForChildrenToComplete) {
447                                 status = TaskStatus.RanToCompletion;
448                                 ProcessChildExceptions ();
449                                 ProcessCompleteDelegates ();
450                         }
451                 }
452
453                 internal virtual void InnerInvoke ()
454                 {
455                         if (action == null && simpleAction != null)
456                                 simpleAction ();
457                         else if (action != null)
458                                 action (state);
459                         // Set action to null so that the GC can collect the delegate and thus
460                         // any big object references that the user might have captured in an anonymous method
461                         action = null;
462                         simpleAction = null;
463                         state = null;
464                 }
465                 
466                 internal void Finish ()
467                 {
468                         // If there wasn't any child created in the task we set the CountdownEvent
469                         childTasks.Signal ();
470                         
471                         // Don't override Canceled or Faulted
472                         if (status == TaskStatus.Running) {
473                                 if (childTasks.IsSet)
474                                         status = TaskStatus.RanToCompletion;
475                                 else
476                                         status = TaskStatus.WaitingForChildrenToComplete;
477                         }
478                 
479                         if (status != TaskStatus.WaitingForChildrenToComplete)
480                                 ProcessCompleteDelegates ();
481                         
482                         // Reset the current thingies
483                         current = null;
484                         TaskScheduler.Current = null;
485                         
486                         // Tell parent that we are finished
487                         if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.AttachedToParent) && parent != null) {
488                                 parent.ChildCompleted (this.Exception);
489                         }
490                 }
491
492                 void ProcessCompleteDelegates ()
493                 {
494                         if (completed == null)
495                                 return;
496
497                         EventHandler handler;
498                         while (completed.TryDequeue (out handler))
499                                 handler (this, EventArgs.Empty);
500                 }
501
502                 void ProcessChildExceptions ()
503                 {
504                         if (childExceptions == null)
505                                 return;
506
507                         if (exception == null)
508                                 exception = new AggregateException ();
509
510                         AggregateException childEx;
511                         while (childExceptions.TryDequeue (out childEx))
512                                 exception.AddChildException (childEx);
513                 }
514                 #endregion
515                 
516                 #region Cancel and Wait related method
517                 
518                 internal void CancelReal ()
519                 {
520                         status = TaskStatus.Canceled;
521                 }
522
523                 internal void HandleGenericException (Exception e)
524                 {
525                         HandleGenericException (new AggregateException (e));
526                 }
527
528                 internal void HandleGenericException (AggregateException e)
529                 {
530                         exception = e;
531                         Thread.MemoryBarrier ();
532                         status = TaskStatus.Faulted;
533                         if (scheduler != null && scheduler.FireUnobservedEvent (exception).Observed)
534                                 exceptionObserved = true;
535                 }
536                 
537                 public void Wait ()
538                 {
539                         if (scheduler == null)
540                                 schedWait.Wait ();
541                         
542                         if (!IsCompleted)
543                                 scheduler.ParticipateUntil (this);
544                         if (exception != null)
545                                 throw exception;
546                         if (IsCanceled)
547                                 throw new AggregateException (new TaskCanceledException (this));
548                 }
549
550                 public void Wait (CancellationToken cancellationToken)
551                 {
552                         Wait (-1, cancellationToken);
553                 }
554                 
555                 public bool Wait (TimeSpan timeout)
556                 {
557                         return Wait (CheckTimeout (timeout), CancellationToken.None);
558                 }
559                 
560                 public bool Wait (int millisecondsTimeout)
561                 {
562                         return Wait (millisecondsTimeout, CancellationToken.None);
563                 }
564
565                 public bool Wait (int millisecondsTimeout, CancellationToken cancellationToken)
566                 {
567                         if (millisecondsTimeout < -1)
568                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
569
570                         if (millisecondsTimeout == -1 && token == CancellationToken.None) {
571                                 Wait ();
572                                 return true;
573                         }
574
575                         Watch watch = Watch.StartNew ();
576
577                         if (scheduler == null) {
578                                 schedWait.Wait (millisecondsTimeout, cancellationToken);
579                                 millisecondsTimeout = ComputeTimeout (millisecondsTimeout, watch);
580                         }
581
582                         ManualResetEventSlim predicateEvt = new ManualResetEventSlim (false);
583                         if (cancellationToken != CancellationToken.None) {
584                                 cancellationToken.Register (predicateEvt.Set);
585                                 cancellationToken.ThrowIfCancellationRequested ();
586                         }
587
588                         bool result = scheduler.ParticipateUntil (this, predicateEvt, millisecondsTimeout);
589
590                         if (exception != null)
591                                 throw exception;
592                         if (IsCanceled)
593                                 throw new AggregateException (new TaskCanceledException (this));
594                         
595                         return !result;
596                 }
597                 
598                 public static void WaitAll (params Task[] tasks)
599                 {
600                         if (tasks == null)
601                                 throw new ArgumentNullException ("tasks");
602                         
603                         foreach (var t in tasks) {
604                                 if (t == null)
605                                         throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
606                                 t.Wait ();
607                         }
608                 }
609
610                 public static void WaitAll (Task[] tasks, CancellationToken cancellationToken)
611                 {
612                         if (tasks == null)
613                                 throw new ArgumentNullException ("tasks");
614                         
615                         foreach (var t in tasks) {
616                                 if (t == null)
617                                         throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
618
619                                 t.Wait (cancellationToken);
620                         }
621                 }
622                 
623                 public static bool WaitAll (Task[] tasks, TimeSpan timeout)
624                 {
625                         if (tasks == null)
626                                 throw new ArgumentNullException ("tasks");
627                         
628                         bool result = true;
629                         foreach (var t in tasks) {
630                                 if (t == null)
631                                         throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
632
633                                 result &= t.Wait (timeout);
634                         }
635                         return result;
636                 }
637                 
638                 public static bool WaitAll (Task[] tasks, int millisecondsTimeout)
639                 {
640                         if (tasks == null)
641                                 throw new ArgumentNullException ("tasks");
642                         
643                         bool result = true;
644                         foreach (var t in tasks) {
645                                 if (t == null)
646                                         throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
647
648                                 result &= t.Wait (millisecondsTimeout);
649                         }
650                         return result;
651                 }
652                 
653                 public static bool WaitAll (Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
654                 {
655                         if (tasks == null)
656                                 throw new ArgumentNullException ("tasks");
657                         
658                         bool result = true;
659                         foreach (var t in tasks) {
660                                 if (t == null)
661                                         throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
662
663                                 result &= t.Wait (millisecondsTimeout, cancellationToken);
664                         }
665                         return result;
666                 }
667                 
668                 public static int WaitAny (params Task[] tasks)
669                 {
670                         return WaitAny (tasks, -1, CancellationToken.None);
671                 }
672
673                 public static int WaitAny (Task[] tasks, TimeSpan timeout)
674                 {
675                         return WaitAny (tasks, CheckTimeout (timeout));
676                 }
677                 
678                 public static int WaitAny (Task[] tasks, int millisecondsTimeout)
679                 {
680                         if (millisecondsTimeout < -1)
681                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
682
683                         if (millisecondsTimeout == -1)
684                                 return WaitAny (tasks);
685
686                         return WaitAny (tasks, millisecondsTimeout, CancellationToken.None);
687                 }
688
689                 public static int WaitAny (Task[] tasks, CancellationToken cancellationToken)
690                 {
691                         return WaitAny (tasks, -1, cancellationToken);
692                 }
693
694                 public static int WaitAny (Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
695                 {
696                         if (tasks == null)
697                                 throw new ArgumentNullException ("tasks");
698                         if (tasks.Length == 0)
699                                 throw new ArgumentException ("tasks is empty", "tasks");
700                         if (tasks.Length == 1) {
701                                 tasks[0].Wait (millisecondsTimeout, cancellationToken);
702                                 return 0;
703                         }
704                         
705                         int numFinished = 0;
706                         int indexFirstFinished = -1;
707                         int index = 0;
708                         TaskScheduler sched = null;
709                         Task task = null;
710                         Watch watch = Watch.StartNew ();
711                         ManualResetEventSlim predicateEvt = new ManualResetEventSlim (false);
712
713                         foreach (Task t in tasks) {
714                                 int indexResult = index++;
715                                 t.ContinueWith (delegate {
716                                         if (numFinished >= 1)
717                                                 return;
718                                         int result = Interlocked.Increment (ref numFinished);
719
720                                         // Check if we are the first to have finished
721                                         if (result == 1)
722                                                 indexFirstFinished = indexResult;
723
724                                         // Stop waiting
725                                         predicateEvt.Set ();
726                                 }, TaskContinuationOptions.ExecuteSynchronously);
727
728                                 if (sched == null && t.scheduler != null) {
729                                         task = t;
730                                         sched = t.scheduler;
731                                 }
732                         }
733
734                         // If none of task have a scheduler we are forced to wait for at least one to start
735                         if (sched == null) {
736                                 var handles = Array.ConvertAll (tasks, t => t.schedWait.WaitHandle);
737                                 int shandle = -1;
738                                 if ((shandle = WaitHandle.WaitAny (handles, millisecondsTimeout)) == WaitHandle.WaitTimeout)
739                                         return -1;
740                                 sched = tasks[shandle].scheduler;
741                                 task = tasks[shandle];
742                                 millisecondsTimeout = ComputeTimeout (millisecondsTimeout, watch);
743                         }
744
745                         // One task already finished
746                         if (indexFirstFinished != -1)
747                                 return indexFirstFinished;
748
749                         if (cancellationToken != CancellationToken.None) {
750                                 cancellationToken.Register (predicateEvt.Set);
751                                 cancellationToken.ThrowIfCancellationRequested ();
752                         }
753
754                         sched.ParticipateUntil (task, predicateEvt, millisecondsTimeout);
755
756                         // Index update is still not done
757                         if (indexFirstFinished == -1) {
758                                 SpinWait wait = new SpinWait ();
759                                 while (indexFirstFinished == -1)
760                                         wait.SpinOnce ();
761                         }
762
763                         return indexFirstFinished;
764                 }
765
766                 static int CheckTimeout (TimeSpan timeout)
767                 {
768                         try {
769                                 return checked ((int)timeout.TotalMilliseconds);
770                         } catch (System.OverflowException) {
771                                 throw new ArgumentOutOfRangeException ("timeout");
772                         }
773                 }
774
775                 static int ComputeTimeout (int millisecondsTimeout, Watch watch)
776                 {
777                         return millisecondsTimeout == -1 ? -1 : (int)Math.Max (watch.ElapsedMilliseconds - millisecondsTimeout, 1);
778                 }
779
780                 #endregion
781                 
782                 #region Dispose
783                 public void Dispose ()
784                 {
785                         Dispose (true);
786                 }
787                 
788                 protected virtual void Dispose (bool disposing)
789                 {
790                         if (!IsCompleted)
791                                 throw new InvalidOperationException ("A task may only be disposed if it is in a completion state");
792
793                         // Set action to null so that the GC can collect the delegate and thus
794                         // any big object references that the user might have captured in a anonymous method
795                         if (disposing) {
796                                 action = null;
797                                 state = null;
798                         }
799                 }
800                 #endregion
801                 
802                 #region Properties
803                 public static TaskFactory Factory {
804                         get {
805                                 return defaultFactory;
806                         }
807                 }
808                 
809                 public static int? CurrentId {
810                         get {
811                                 Task t = current;
812                                 return t == null ? (int?)null : t.Id;
813                         }
814                 }
815                 
816                 public AggregateException Exception {
817                         get {
818                                 exceptionObserved = true;
819                                 
820                                 return exception;       
821                         }
822                         internal set {
823                                 exception = value;
824                         }
825                 }
826                 
827                 public bool IsCanceled {
828                         get {
829                                 return status == TaskStatus.Canceled;
830                         }
831                 }
832
833                 public bool IsCompleted {
834                         get {
835                                 return status == TaskStatus.RanToCompletion ||
836                                         status == TaskStatus.Canceled || status == TaskStatus.Faulted;
837                         }
838                 }
839                 
840                 public bool IsFaulted {
841                         get {
842                                 return status == TaskStatus.Faulted;
843                         }
844                 }
845
846                 public TaskCreationOptions CreationOptions {
847                         get {
848                                 return taskCreationOptions;
849                         }
850                 }
851                 
852                 public TaskStatus Status {
853                         get {
854                                 return status;
855                         }
856                         internal set {
857                                 status = value;
858                         }
859                 }
860
861                 public object AsyncState {
862                         get {
863                                 return state;
864                         }
865                 }
866                 
867                 bool IAsyncResult.CompletedSynchronously {
868                         get {
869                                 return true;
870                         }
871                 }
872
873                 WaitHandle IAsyncResult.AsyncWaitHandle {
874                         get {
875                                 return null;
876                         }
877                 }
878                 
879                 public int Id {
880                         get {
881                                 return taskId;
882                         }
883                 }
884
885                 internal Task Parent {
886                         get {
887                                 return parent;
888                         }
889                 }
890                 
891                 string DisplayActionMethod {
892                         get {
893                                 Delegate d = simpleAction ?? (Delegate) action;
894                                 return d == null ? "<none>" : d.Method.ToString ();
895                         }
896                 }
897                 
898                 #endregion
899         }
900 }
901 #endif