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