2010-01-20 Zoltan Varga <vargaz@gmail.com>
[mono.git] / mcs / class / corlib / System.Threading.Tasks / Task.cs
1 #if NET_4_0
2 // Task.cs
3 //
4 // Copyright (c) 2008 Jérémie "Garuma" Laval
5 //
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:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
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
22 // THE SOFTWARE.
23 //
24 //
25
26 using System;
27 using System.Threading;
28 using System.Collections.Concurrent;
29
30 namespace System.Threading.Tasks
31 {
32         public class Task : IDisposable, IAsyncResult
33         {
34                 // With this attribute each thread has its own value so that it's correct for our Schedule code
35                 // and for Parent property.
36                 [System.ThreadStatic]
37                 static Task         current;
38                 [System.ThreadStatic]
39                 static Action<Task> childWorkAdder;
40                 
41                 static int          id = -1;
42                 static TaskFactory  defaultFactory = new TaskFactory ();
43                 
44                 CountdownEvent childTasks = new CountdownEvent (1);
45                 
46                 Task parent = current;
47                 
48                 int                 taskId;
49                 bool                respectParentCancellation;
50                 TaskCreationOptions taskCreationOptions;
51                 
52                 IScheduler          scheduler;
53                 TaskScheduler       taskScheduler;
54                 
55                 volatile Exception  exception;
56                 volatile bool       exceptionObserved;
57                 volatile TaskStatus status;
58                 
59                 Action<object> action;
60                 object         state;
61                 EventHandler   completed;
62                 
63                 CancellationTokenSource src = new CancellationTokenSource ();
64                         
65                 
66                 public Task (Action action) : this (action, TaskCreationOptions.None)
67                 {
68                         
69                 }
70                 
71                 public Task (Action action, TaskCreationOptions options) : this ((o) => action (), null, options)
72                 {
73                         
74                 }
75                 
76                 public Task (Action<object> action, object state) : this (action, state, TaskCreationOptions.None)
77                 {
78                         
79                 }
80                 
81                 public Task (Action<object> action, object state, TaskCreationOptions options)
82                 {
83                         this.taskCreationOptions = options;
84                         this.action = action == null ? EmptyFunc : action;
85                         this.state = state;
86                         this.taskId = Interlocked.Increment (ref id);
87                         this.status = TaskStatus.Created;
88
89                         // Process taskCreationOptions
90                         if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.DetachedFromParent))
91                                 parent = null;
92                         else if (parent != null)
93                                 parent.AddChild ();
94
95                         respectParentCancellation =
96                                 CheckTaskOptions (taskCreationOptions, TaskCreationOptions.RespectParentCancellation);
97                 }
98                 
99                 ~Task ()
100                 {
101                         if (exception != null && !exceptionObserved)
102                                 throw exception;
103                 }
104
105                 bool CheckTaskOptions (TaskCreationOptions opt, TaskCreationOptions member)
106                 {
107                         return (opt & member) == member;
108                 }
109
110                 static void EmptyFunc (object o)
111                 {
112                 }
113                 
114                 #region Start
115                 public void Start ()
116                 {
117                         Start (TaskScheduler.Current);
118                 }
119                 
120                 public void Start (TaskScheduler tscheduler)
121                 {
122                         this.taskScheduler = tscheduler;
123                         Start (ProxifyScheduler (tscheduler));
124                 }
125                 
126                 void Start (IScheduler scheduler)
127                 {
128                         this.scheduler = scheduler;
129                         status = TaskStatus.WaitingForActivation;
130                         Schedule ();
131                 }
132                 
133                 IScheduler ProxifyScheduler (TaskScheduler tscheduler)
134                 {
135                         IScheduler sched = tscheduler as IScheduler;
136                         return sched != null ? sched : new SchedulerProxy (tscheduler);
137                 }
138                 
139                 public void RunSynchronously ()
140                 {
141                         RunSynchronously (TaskScheduler.Current);
142                 }
143                 
144                 public void RunSynchronously (TaskScheduler tscheduler) 
145                 {
146                         // Adopt this scheme for the moment
147                         ThreadStart ();
148                 }
149                 #endregion
150                 
151                 #region ContinueWith
152                 public Task ContinueWith (Action<Task> a)
153                 {
154                         return ContinueWith (a, TaskContinuationOptions.None);
155                 }
156                 
157                 public Task ContinueWith (Action<Task> a, TaskContinuationOptions kind)
158                 {
159                         return ContinueWith (a, kind, TaskScheduler.Current);
160                 }
161                 
162                 public Task ContinueWith (Action<Task> a, TaskScheduler scheduler)
163                 {
164                         return ContinueWith (a, TaskContinuationOptions.None, scheduler);
165                 }
166                 
167                 public Task ContinueWith (Action<Task> a, TaskContinuationOptions kind, TaskScheduler scheduler)
168                 {
169                         Task continuation = new Task ((o) => a ((Task)o), this, GetCreationOptions (kind));
170                         ContinueWithCore (continuation, kind, scheduler);
171                         return continuation;
172                 }
173                 
174                 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> a)
175                 {
176                         return ContinueWith<TResult> (a, TaskContinuationOptions.None);
177                 }
178                 
179                 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> a, TaskContinuationOptions options)
180                 {
181                         return ContinueWith<TResult> (a, options, TaskScheduler.Current);
182                 }
183                 
184                 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> a, TaskScheduler scheduler)
185                 {
186                         return ContinueWith<TResult> (a, TaskContinuationOptions.None, scheduler);
187                 }
188                 
189                 public Task<TResult> ContinueWith<TResult> (Func<Task, TResult> a, TaskContinuationOptions kind, TaskScheduler scheduler)
190                 {
191                         Task<TResult> t = new Task<TResult> ((o) => a ((Task)o), this, GetCreationOptions (kind));
192                         
193                         ContinueWithCore (t, kind, scheduler);
194                         
195                         return t;
196                 }
197                 
198                 internal void ContinueWithCore (Task continuation, TaskContinuationOptions kind, TaskScheduler scheduler)
199                 {
200                         ContinueWithCore (continuation, kind, scheduler, () => true);
201                 }
202                 
203                 internal void ContinueWithCore (Task continuation, TaskContinuationOptions kind,
204                                                 TaskScheduler scheduler, Func<bool> predicate)
205                 {
206                         // Already set the scheduler so that user can call Wait and that sort of stuff
207                         continuation.taskScheduler = scheduler;
208                         continuation.scheduler = ProxifyScheduler (scheduler);
209                         
210                         AtomicBoolean launched = new AtomicBoolean ();
211                         EventHandler action = delegate {
212                                 if (!predicate ()) return;
213                                 
214                                 if (!launched.Value && !launched.Exchange (true)) {
215                                         if (!ContinuationStatusCheck (kind)) {
216                                                 continuation.Cancel ();
217                                                 continuation.CancelReal ();
218                                                 continuation.Dispose ();
219                                                 
220                                                 return;
221                                         }
222                                         
223                                         CheckAndSchedule (continuation, kind, scheduler);
224                                 }
225                         };
226                         
227                         if (IsCompleted) {
228                                 action (this, EventArgs.Empty);
229                                 return;
230                         }
231                         
232                         completed += action;
233                         
234                         // Retry in case completion was achieved but event adding was too late
235                         if (IsCompleted)
236                                 action (this, EventArgs.Empty);
237                 }
238                 
239                 bool ContinuationStatusCheck (TaskContinuationOptions kind)
240                 {
241                         if (kind == TaskContinuationOptions.None)
242                                 return true;
243                         
244                         int kindCode = (int)kind;
245                         
246                         if (kindCode >= ((int)TaskContinuationOptions.NotOnRanToCompletion)) {
247                                 if (status == TaskStatus.Canceled) {
248                                         if (kind == TaskContinuationOptions.NotOnCanceled)
249                                                 return false;
250                                         if (kind == TaskContinuationOptions.OnlyOnFaulted)
251                                                 return false;
252                                         if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
253                                                 return false;
254                                 } else if (status == TaskStatus.Faulted) {
255                                         if (kind == TaskContinuationOptions.NotOnFaulted)
256                                                 return false;
257                                         if (kind == TaskContinuationOptions.OnlyOnCanceled)
258                                                 return false;
259                                         if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
260                                                 return false;
261                                 } else if (status == TaskStatus.RanToCompletion) {
262                                         if (kind == TaskContinuationOptions.NotOnRanToCompletion)
263                                                 return false;
264                                         if (kind == TaskContinuationOptions.OnlyOnFaulted)
265                                                 return false;
266                                         if (kind == TaskContinuationOptions.OnlyOnCanceled)
267                                                 return false;
268                                 }
269                         }
270                         
271                         return true;
272                 }
273                 
274                 void CheckAndSchedule (Task continuation, TaskContinuationOptions options, TaskScheduler scheduler)
275                 {
276                         if (options == TaskContinuationOptions.None || (options & TaskContinuationOptions.ExecuteSynchronously) > 0) {
277                                 continuation.ThreadStart ();
278                         } else {
279                                 continuation.Start (scheduler);
280                         }
281                 }
282                 
283                 static TaskCreationOptions GetCreationOptions (TaskContinuationOptions kind)
284                 {
285                         TaskCreationOptions options = TaskCreationOptions.None;
286                         if ((kind & TaskContinuationOptions.DetachedFromParent) > 0)
287                                 options |= TaskCreationOptions.DetachedFromParent;
288                         if ((kind & TaskContinuationOptions.RespectParentCancellation) > 0)
289                                 options |= TaskCreationOptions.RespectParentCancellation;
290                         
291                         return options;
292                 }
293                 #endregion
294                 
295                 #region Internal and protected thingies
296                 internal void Schedule ()
297                 {       
298                         status = TaskStatus.WaitingToRun;
299                         
300                         // If worker is null it means it is a local one, revert to the old behavior
301                         if (current == null || childWorkAdder == null || parent == null
302                             || CheckTaskOptions (taskCreationOptions, TaskCreationOptions.PreferFairness)) {
303                                 
304                                 scheduler.AddWork (this);
305                                 
306                         } else {
307                                 /* Like the semantic of the ABP paper describe it, we add ourselves to the bottom 
308                                  * of our Parent Task's ThreadWorker deque. It's ok to do that since we are in
309                                  * the correct Thread during the creation
310                                  */
311                                 childWorkAdder (this);
312                         }
313                 }
314                 
315                 void ThreadStart ()
316                 {                       
317                         current = this;
318                         TaskScheduler.Current = taskScheduler;
319                         
320                         if (!src.IsCancellationRequested
321                             && (!respectParentCancellation || (respectParentCancellation && parent != null && !parent.IsCanceled))) {
322                                 
323                                 status = TaskStatus.Running;
324                                 
325                                 try {
326                                         InnerInvoke ();
327                                 } catch (Exception e) {
328                                         exception = e;
329                                         status = TaskStatus.Faulted;
330                                 }
331                         } else {
332                                 AcknowledgeCancellation ();
333                         }
334                         
335                         Finish ();
336                 }
337                 
338                 internal void Execute (Action<Task> childAdder)
339                 {
340                         childWorkAdder = childAdder;
341                         ThreadStart ();
342                 }
343                 
344                 internal void AddChild ()
345                 {
346                         childTasks.AddCount ();
347                 }
348
349                 internal void ChildCompleted ()
350                 {
351                         childTasks.Signal ();
352                         if (childTasks.IsSet && status == TaskStatus.WaitingForChildrenToComplete)
353                                 status = TaskStatus.RanToCompletion;
354                 }
355
356                 internal virtual void InnerInvoke ()
357                 {
358                         if (action != null)
359                                 action (state);
360                         // Set action to null so that the GC can collect the delegate and thus
361                         // any big object references that the user might have captured in an anonymous method
362                         action = null;
363                         state = null;
364                 }
365                 
366                 internal void Finish ()
367                 {
368                         // If there wasn't any child created in the task we set the CountdownEvent
369                         childTasks.Signal ();
370                         
371                         // Don't override Canceled or Faulted
372                         if (status == TaskStatus.Running) {
373                                 if (childTasks.IsSet )
374                                         status = TaskStatus.RanToCompletion;
375                                 else
376                                         status = TaskStatus.WaitingForChildrenToComplete;
377                         }
378                         
379                         // Call the event in the correct style
380                         EventHandler tempCompleted = completed;
381                         if (tempCompleted != null) 
382                                 tempCompleted (this, EventArgs.Empty);
383                         
384                         // Reset the current thingies
385                         current = null;
386                         TaskScheduler.Current = null;
387                         
388                         // Tell parent that we are finished
389                         if (!CheckTaskOptions (taskCreationOptions, TaskCreationOptions.DetachedFromParent) && parent != null){
390                                 parent.ChildCompleted ();
391                         }
392                         
393                         Dispose ();
394                 }
395                 #endregion
396                 
397                 #region Cancel and Wait related methods
398                 public void AcknowledgeCancellation ()
399                 {
400                         if (this != current)
401                                 throw new InvalidOperationException ("The Task object is different from the currently executing"
402                                                                      + "task or the current task hasn't been "
403                                                                      + "marked for cancellation.");
404                         
405                         CancelReal ();
406                 }
407                 
408                 internal void CancelReal ()
409                 {
410                         exception = new TaskCanceledException (this);
411                         status = TaskStatus.Canceled;
412                 }
413                 
414                 public void Cancel ()
415                 {
416                         src.Cancel ();
417                 }
418                 
419                 public void CancelAndWait ()
420                 {
421                         Cancel ();
422                         Wait ();
423                 }
424                 
425                 public void CancelAndWait (CancellationToken token)
426                 {
427                         Cancel ();
428                         Wait (token);
429                 }
430                 
431                 public bool CancelAndWait (TimeSpan ts)
432                 {
433                         Cancel ();
434                         return Wait (ts);
435                 }
436                 
437                 public bool CancelAndWait (int millisecondsTimeout)
438                 {
439                         Cancel ();
440                         return Wait (millisecondsTimeout);
441                 }
442                 
443                 public bool CancelAndWait (int millisecondsTimeout, CancellationToken token)
444                 {
445                         Cancel ();
446                         return Wait (millisecondsTimeout, token);
447                 }
448                 
449                 public void Wait ()
450                 {
451                         if (scheduler == null)
452                                 throw new InvalidOperationException ("The Task hasn't been Started and thus can't be waited on");
453                         
454                         scheduler.ParticipateUntil (this);
455                         if (exception != null && !(exception is TaskCanceledException))
456                                 throw exception;
457                 }
458
459                 public void Wait (CancellationToken token)
460                 {
461                         Wait (null, token);
462                 }
463                 
464                 public bool Wait (TimeSpan ts)
465                 {
466                         return Wait ((int)ts.TotalMilliseconds);
467                 }
468                 
469                 public bool Wait (int millisecondsTimeout)
470                 {
471                         Watch sw = Watch.StartNew ();
472                         return Wait (() => sw.ElapsedMilliseconds >= millisecondsTimeout, null);
473                 }
474                 
475                 public bool Wait (int millisecondsTimeout, CancellationToken token)
476                 {
477                         Watch sw = Watch.StartNew ();
478                         return Wait (() => sw.ElapsedMilliseconds >= millisecondsTimeout, token);
479                 }
480
481                 bool Wait (Func<bool> stopFunc, CancellationToken? token)
482                 {
483                         if (scheduler == null)
484                                 throw new InvalidOperationException ("The Task hasn't been Started and thus can't be waited on");
485                         
486                         bool result = scheduler.ParticipateUntil (this, delegate { 
487                                 if (token.HasValue && token.Value.IsCancellationRequested)
488                                         throw new OperationCanceledException ("The CancellationToken has had cancellation requested.");
489                                 
490                                 
491                                 return (stopFunc != null) ? stopFunc () : false;
492                         });
493
494                         if (exception != null && !(exception is TaskCanceledException))
495                                 throw exception;
496                         
497                         return !result;
498                 }
499                 
500                 public static void WaitAll (params Task[] tasks)
501                 {
502                         if (tasks == null)
503                                 throw new ArgumentNullException ("tasks");
504                         if (tasks.Length == 0)
505                                 throw new ArgumentException ("tasks is empty", "tasks");
506                         
507                         foreach (var t in tasks)
508                                 t.Wait ();
509                 }
510
511                 public static void WaitAll (Task[] tasks, CancellationToken token)
512                 {
513                         if (tasks == null)
514                                 throw new ArgumentNullException ("tasks");
515                         if (tasks.Length == 0)
516                                 throw new ArgumentException ("tasks is empty", "tasks");
517                         
518                         foreach (var t in tasks)
519                                 t.Wait (token);
520                 }
521                 
522                 public static bool WaitAll (Task[] tasks, TimeSpan ts)
523                 {
524                         if (tasks == null)
525                                 throw new ArgumentNullException ("tasks");
526                         if (tasks.Length == 0)
527                                 throw new ArgumentException ("tasks is empty", "tasks");
528                         
529                         bool result = true;
530                         foreach (var t in tasks)
531                                 result &= t.Wait (ts);
532                         return result;
533                 }
534                 
535                 public static bool WaitAll (Task[] tasks, int millisecondsTimeout)
536                 {
537                         if (tasks == null)
538                                 throw new ArgumentNullException ("tasks");
539                         if (tasks.Length == 0)
540                                 throw new ArgumentException ("tasks is empty", "tasks");
541                         
542                         bool result = true;
543                         foreach (var t in tasks)
544                                 result &= t.Wait (millisecondsTimeout);
545                         return result;
546                 }
547                 
548                 public static bool WaitAll (Task[] tasks, int millisecondsTimeout, CancellationToken token)
549                 {
550                         if (tasks == null)
551                                 throw new ArgumentNullException ("tasks");
552                         if (tasks.Length == 0)
553                                 throw new ArgumentException ("tasks is empty", "tasks");
554                         
555                         bool result = true;
556                         foreach (var t in tasks)
557                                 result &= t.Wait (millisecondsTimeout, token);
558                         return result;
559                 }
560                 
561                 public static int WaitAny (params Task[] tasks)
562                 {
563                         return WaitAny (tasks, null, null);
564                 }
565                 
566                 static int WaitAny (Task[] tasks, Func<bool> stopFunc, CancellationToken? token)
567                 {
568                         if (tasks == null)
569                                 throw new ArgumentNullException ("tasks");
570                         if (tasks.Length == 0)
571                                 throw new ArgumentException ("tasks is empty", "tasks");
572                         
573                         int numFinished = 0;
574                         int indexFirstFinished = -1;
575                         int index = 0;
576                         
577                         foreach (Task t in tasks) {
578                                 t.ContinueWith (delegate {
579                                         int indexResult = index;
580                                         int result = Interlocked.Increment (ref numFinished);
581                                         // Check if we are the first to have finished
582                                         if (result == 1)
583                                                 indexFirstFinished = indexResult;
584                                 });     
585                                 index++;
586                         }
587                         
588                         // One task already finished
589                         if (indexFirstFinished != -1)
590                                 return indexFirstFinished;
591                         
592                         // All tasks are supposed to use the same TaskManager
593                         tasks[0].scheduler.ParticipateUntil (delegate {
594                                 if (stopFunc != null && stopFunc ())
595                                         return true;
596                                 
597                                 if (token.HasValue && token.Value.IsCancellationRequested)
598                                         throw new OperationCanceledException ("The CancellationToken has had cancellation requested.");
599                                 
600                                 return numFinished >= 1;
601                         });
602                         
603                         return indexFirstFinished;
604                 }
605                 
606                 public static int WaitAny (Task[] tasks, TimeSpan ts)
607                 {
608                         return WaitAny (tasks, (int)ts.TotalMilliseconds);
609                 }
610                 
611                 public static int WaitAny (Task[] tasks, int millisecondsTimeout)
612                 {
613                         if (millisecondsTimeout < -1)
614                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
615                         
616                         if (millisecondsTimeout == -1)
617                                 return WaitAny (tasks);
618                         
619                         Watch sw = Watch.StartNew ();
620                         return WaitAny (tasks, () => sw.ElapsedMilliseconds > millisecondsTimeout, null);
621                 }
622
623                 public static int WaitAny (Task[] tasks, int millisecondsTimeout, CancellationToken token)
624                 {                       
625                         if (millisecondsTimeout < -1)
626                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout");
627                         
628                         if (millisecondsTimeout == -1)
629                                 return WaitAny (tasks);
630                         
631                         Watch sw = Watch.StartNew ();
632                         return WaitAny (tasks, () => sw.ElapsedMilliseconds > millisecondsTimeout, token);
633                 }
634                 
635                 public static int WaitAny (Task[] tasks, CancellationToken token)
636                 {                       
637                         return WaitAny (tasks, null, token);
638                 }
639                 #endregion
640                 
641                 #region Dispose
642                 public void Dispose ()
643                 {
644                         Dispose (true);
645                 }
646                 
647                 protected virtual void Dispose (bool disposeManagedRes)
648                 {
649                         // Set action to null so that the GC can collect the delegate and thus
650                         // any big object references that the user might have captured in a anonymous method
651                         if (disposeManagedRes) {
652                                 action = null;
653                                 completed = null;
654                                 state = null;
655                         }
656                 }
657                 #endregion
658                 
659                 #region Properties
660                 public static TaskFactory Factory {
661                         get {
662                                 return defaultFactory;
663                         }
664                 }
665                 
666                 public static Task Current {
667                         get {
668                                 return current;
669                         }
670                 }
671                 
672                 public CancellationToken CancellationToken {
673                         get {
674                                 return src.Token;
675                         }
676                 }
677                 
678                 public Exception Exception {
679                         get {
680                                 exceptionObserved = true;
681                                 
682                                 return exception;       
683                         }
684                         internal set {
685                                 exception = value;
686                         }
687                 }
688                 
689                 public bool IsCanceled {
690                         get {
691                                 return status == TaskStatus.Canceled;
692                         }
693                 }
694
695                 public bool IsCancellationRequested {
696                         get {
697                                 return src.IsCancellationRequested;
698                         }
699                 }
700
701                 public bool IsCompleted {
702                         get {
703                                 return status == TaskStatus.RanToCompletion ||
704                                         status == TaskStatus.Canceled || status == TaskStatus.Faulted;
705                         }
706                 }
707                 
708                 public bool IsFaulted {
709                         get {
710                                 return status == TaskStatus.Faulted;
711                         }
712                 }
713
714                 public Task Parent {
715                         get {
716                                 return parent;
717                         }
718                 }
719
720                 public TaskCreationOptions CreationOptions {
721                         get {
722                                 return taskCreationOptions;
723                         }
724                 }
725                 
726                 public TaskStatus Status {
727                         get {
728                                 return status;
729                         }
730                         internal set {
731                                 status = value;
732                         }
733                 }
734
735                 public object AsyncState {
736                         get {
737                                 return state;
738                         }
739                 }
740                 
741                 bool IAsyncResult.CompletedSynchronously {
742                         get {
743                                 return true;
744                         }
745                 }
746
747                 WaitHandle IAsyncResult.AsyncWaitHandle {
748                         get {
749                                 return null;
750                         }
751                 }
752                 
753                 public int Id {
754                         get {
755                                 return taskId;
756                         }
757                 }
758                 #endregion
759         }
760 }
761 #endif