Merge pull request #980 from StephenMcConnel/bug-18638
[mono.git] / mcs / class / corlib / System.Threading.Tasks / TaskContinuation.cs
1 //
2 // TaskContinuation.cs
3 //
4 // Authors:
5 //    Jérémie Laval <jeremie dot laval at xamarin dot com>
6 //    Marek Safar  <marek.safar@gmail.com>
7 //
8 // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28 //
29
30 #if NET_4_0
31
32 using System.Collections.Generic;
33
34 namespace System.Threading.Tasks
35 {
36         interface IContinuation
37         {
38                 void Execute ();
39         }
40
41         class TaskContinuation : IContinuation
42         {
43                 readonly Task task;
44                 readonly TaskContinuationOptions continuationOptions;
45
46                 public TaskContinuation (Task task, TaskContinuationOptions continuationOptions)
47                 {
48                         this.task = task;
49                         this.continuationOptions = continuationOptions;
50                 }
51
52                 bool ContinuationStatusCheck (TaskContinuationOptions kind)
53                 {
54                         if (kind == TaskContinuationOptions.None)
55                                 return true;
56
57                         int kindCode = (int) kind;
58                         var status = task.ContinuationAncestor.Status;
59
60                         if (kindCode >= ((int) TaskContinuationOptions.NotOnRanToCompletion)) {
61                                 // Remove other options
62                                 kind &= ~(TaskContinuationOptions.PreferFairness
63                                                   | TaskContinuationOptions.LongRunning
64                                                   | TaskContinuationOptions.AttachedToParent
65                                                   | TaskContinuationOptions.ExecuteSynchronously);
66
67                                 if (status == TaskStatus.Canceled) {
68                                         if (kind == TaskContinuationOptions.NotOnCanceled)
69                                                 return false;
70                                         if (kind == TaskContinuationOptions.OnlyOnFaulted)
71                                                 return false;
72                                         if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
73                                                 return false;
74                                 } else if (status == TaskStatus.Faulted) {
75                                         if (kind == TaskContinuationOptions.NotOnFaulted)
76                                                 return false;
77                                         if (kind == TaskContinuationOptions.OnlyOnCanceled)
78                                                 return false;
79                                         if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
80                                                 return false;
81                                 } else if (status == TaskStatus.RanToCompletion) {
82                                         if (kind == TaskContinuationOptions.NotOnRanToCompletion)
83                                                 return false;
84                                         if (kind == TaskContinuationOptions.OnlyOnFaulted)
85                                                 return false;
86                                         if (kind == TaskContinuationOptions.OnlyOnCanceled)
87                                                 return false;
88                                 }
89                         }
90
91                         return true;
92                 }
93
94                 public void Execute ()
95                 {
96                         if (!ContinuationStatusCheck (continuationOptions)) {
97                                 task.CancelReal (notifyParent : true);
98                                 task.Dispose ();
99                                 return;
100                         }
101
102                         // The task may have been canceled externally
103                         if (task.IsCompleted)
104                                 return;
105
106                         if ((continuationOptions & TaskContinuationOptions.ExecuteSynchronously) != 0)
107                                 task.RunSynchronouslyCore (task.scheduler, false);
108                         else
109                                 task.Schedule (false);
110                 }
111         }
112
113         class AwaiterActionContinuation : IContinuation
114         {
115                 readonly Action action;
116                 readonly ExecutionContext ec;
117
118                 public AwaiterActionContinuation (Action action)
119                 {
120                         this.action = action;
121
122                         // Capture execution context because the continuation can be inlined
123                         // and we still need to run in original exection context regardless
124                         // of UnsafeOnCompleted/OnCompleted entry
125                         ec = ExecutionContext.Capture (false, true);                    
126                 }
127
128                 public void Execute ()
129                 {
130                         //
131                         // Continuation can be inlined only when the current context allows it. This is different to awaiter setup
132                         // because the context where the awaiter task is set to completed can be anywhere (due to TaskCompletionSource)
133                         //
134                         if ((SynchronizationContext.Current == null || SynchronizationContext.Current.GetType () == typeof (SynchronizationContext)) && TaskScheduler.IsDefault) {
135                                 if (ec != null)
136                                         ExecutionContext.Run (ec, l => ((Action)l) (), action);
137                                 else
138                                         action ();
139                         } else {
140                                 ThreadPool.UnsafeQueueUserWorkItem (l => ((Action) l) (), action);
141                         }
142                 }
143         }
144
145         class SchedulerAwaitContinuation : IContinuation
146         {
147                 readonly Task task;
148
149                 public SchedulerAwaitContinuation (Task task)
150                 {
151                         this.task = task;
152                 }
153
154                 public void Execute ()
155                 {
156                         task.RunSynchronouslyCore (task.scheduler, true);
157                 }
158         }
159
160         class SynchronizationContextContinuation : IContinuation
161         {
162                 readonly Action action;
163                 readonly SynchronizationContext ctx;
164
165                 public SynchronizationContextContinuation (Action action, SynchronizationContext ctx)
166                 {
167                         this.action = action;
168                         this.ctx = ctx;
169                 }
170
171                 public void Execute ()
172                 {
173                         // No context switch when we are on correct context
174                         if (ctx == SynchronizationContext.Current)
175                                 action ();
176                         else
177                                 ctx.Post (l => ((Action) l) (), action);
178                 }
179         }
180
181         sealed class WhenAllContinuation : IContinuation
182         {
183                 readonly Task owner;
184                 readonly IList<Task> tasks;
185                 int counter;
186
187                 public WhenAllContinuation (Task owner, IList<Task> tasks)
188                 {
189                         this.owner = owner;
190                         this.counter = tasks.Count;
191                         this.tasks = tasks;
192                 }
193
194                 public void Execute ()
195                 {
196                         if (Interlocked.Decrement (ref counter) != 0)
197                                 return;
198
199                         owner.Status = TaskStatus.Running;
200
201                         bool canceled = false;
202                         List<Exception> exceptions = null;
203                         foreach (var task in tasks) {
204                                 if (task.IsFaulted) {
205                                         if (exceptions == null)
206                                                 exceptions = new List<Exception> ();
207
208                                         exceptions.AddRange (task.Exception.InnerExceptions);
209                                         continue;
210                                 }
211
212                                 if (task.IsCanceled) {
213                                         canceled = true;
214                                 }
215                         }
216
217                         if (exceptions != null) {
218                                 owner.TrySetException (new AggregateException (exceptions), false, false);
219                                 return;
220                         }
221
222                         if (canceled) {
223                                 owner.CancelReal ();
224                                 return;
225                         }
226
227                         owner.Finish ();
228                 }
229         }
230
231         sealed class WhenAllContinuation<TResult> : IContinuation
232         {
233                 readonly Task<TResult[]> owner;
234                 readonly IList<Task<TResult>> tasks;
235                 int counter;
236
237                 public WhenAllContinuation (Task<TResult[]> owner, IList<Task<TResult>> tasks)
238                 {
239                         this.owner = owner;
240                         this.counter = tasks.Count;
241                         this.tasks = tasks;
242                 }
243
244                 public void Execute ()
245                 {
246                         if (Interlocked.Decrement (ref counter) != 0)
247                                 return;
248
249                         bool canceled = false;
250                         List<Exception> exceptions = null;
251                         TResult[] results = null;
252                         for (int i = 0; i < tasks.Count; ++i) {
253                                 var task = tasks [i];
254                                 if (task.IsFaulted) {
255                                         if (exceptions == null)
256                                                 exceptions = new List<Exception> ();
257
258                                         exceptions.AddRange (task.Exception.InnerExceptions);
259                                         continue;
260                                 }
261
262                                 if (task.IsCanceled) {
263                                         canceled = true;
264                                         continue;
265                                 }
266
267                                 if (results == null) {
268                                         if (canceled || exceptions != null)
269                                                 continue;
270
271                                         results = new TResult[tasks.Count];
272                                 }
273
274                                 results[i] = task.Result;
275                         }
276
277                         if (exceptions != null) {
278                                 owner.TrySetException (new AggregateException (exceptions), false, false);
279                                 return;
280                         }
281
282                         if (canceled) {
283                                 owner.CancelReal ();
284                                 return;
285                         }
286
287                         owner.TrySetResult (results);
288                 }
289         }
290
291         sealed class WhenAnyContinuation<T> : IContinuation where T : Task
292         {
293                 readonly Task<T> owner;
294                 readonly IList<T> tasks;
295                 AtomicBooleanValue executed;
296
297                 public WhenAnyContinuation (Task<T> owner, IList<T> tasks)
298                 {
299                         this.owner = owner;
300                         this.tasks = tasks;
301                         executed = new AtomicBooleanValue ();
302                 }
303
304                 public void Execute ()
305                 {
306                         if (!executed.TryRelaxedSet ())
307                                 return;
308
309                         bool owner_notified = false;
310                         for (int i = 0; i < tasks.Count; ++i) {
311                                 var task = tasks[i];
312                                 if (!task.IsCompleted) {
313                                         task.RemoveContinuation (this);
314                                         continue;
315                                 }
316
317                                 if (owner_notified)
318                                         continue;
319
320                                 owner.TrySetResult (task);
321                                 owner_notified = true;
322                         }
323                 }
324         }
325
326         sealed class ManualResetContinuation : IContinuation, IDisposable
327         {
328                 readonly ManualResetEventSlim evt;
329
330                 public ManualResetContinuation ()
331                 {
332                         this.evt = new ManualResetEventSlim ();
333                 }
334
335                 public ManualResetEventSlim Event {
336                         get {
337                                 return evt;
338                         }
339                 }
340
341                 public void Dispose ()
342                 {
343                         evt.Dispose ();
344                 }
345
346                 public void Execute ()
347                 {
348                         evt.Set ();
349                 }
350         }
351
352         sealed class CountdownContinuation : IContinuation, IDisposable
353         {
354                 readonly CountdownEvent evt;
355                 bool disposed;
356
357                 public CountdownContinuation (int initialCount)
358                 {
359                         this.evt = new CountdownEvent (initialCount);
360                 }
361
362                 public CountdownEvent Event {
363                         get {
364                                 return evt;
365                         }
366                 }
367
368                 public void Dispose ()
369                 {
370                         disposed = true;
371                         Thread.MemoryBarrier ();
372         
373                         evt.Dispose ();
374                 }
375
376                 public void Execute ()
377                 {
378                         // Guard against possible race when continuation is disposed and some tasks may still
379                         // execute it (removal was late and the execution is slower than the Dispose thread)
380                         if (!disposed)
381                                 evt.Signal ();
382                 }
383         }
384
385         sealed class DisposeContinuation : IContinuation
386         {
387                 readonly IDisposable instance;
388
389                 public DisposeContinuation (IDisposable instance)
390                 {
391                         this.instance = instance;
392                 }
393
394                 public void Execute ()
395                 {
396                         instance.Dispose ();
397                 }
398         }
399 }
400
401 #endif