1 // ==++==
2 //
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 //
5 // ==--==
6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
7 //
8 // TaskExtensions.cs
9 //
10 // <OWNER>Microsoft</OWNER>
11 //
12 // Extensions to Task/Task<TResult> classes
13 //
14 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Threading;
19 using System.Threading.Tasks;
20 using System.Diagnostics.Contracts;
21 #if !SILVERLIGHT || FEATURE_NETCORE // Desktop and CoreSys but not CoreCLR
22 using System.Runtime.ExceptionServices;
23 #endif
24 
25 namespace System.Threading.Tasks
26 {
27     /// <summary>
28     /// Provides a set of static (Shared in Visual Basic) methods for working with specific kinds of
29     /// <see cref="System.Threading.Tasks.Task"/> instances.
30     /// </summary>
31     public static class TaskExtensions
32     {
33         /// <summary>
34         /// Creates a proxy <see cref="System.Threading.Tasks.Task">Task</see> that represents the
35         /// asynchronous operation of a Task{Task}.
36         /// </summary>
37         /// <remarks>
38         /// It is often useful to be able to return a Task from a <see cref="System.Threading.Tasks.Task{TResult}">
39         /// Task{TResult}</see>, where the inner Task represents work done as part of the outer Task{TResult}.  However,
40         /// doing so results in a Task{Task}, which, if not dealt with carefully, could produce unexpected behavior.  Unwrap
41         /// solves this problem by creating a proxy Task that represents the entire asynchronous operation of such a Task{Task}.
42         /// </remarks>
43         /// <param name="task">The Task{Task} to unwrap.</param>
44         /// <exception cref="T:System.ArgumentNullException">The exception that is thrown if the
45         /// <paramref name="task"/> argument is null.</exception>
46         /// <returns>A Task that represents the asynchronous operation of the provided Task{Task}.</returns>
Unwrap(this Task<Task> task)47         public static Task Unwrap(this Task<Task> task)
48         {
49             if (task == null) throw new ArgumentNullException("task");
50 #if SILVERLIGHT && !FEATURE_NETCORE // CoreCLR only
51             bool result;
52 
53             // tcs.Task serves as a proxy for task.Result.
54             // AttachedToParent is the only legal option for TCS-style task.
55             var tcs = new TaskCompletionSource<Task>(task.CreationOptions & TaskCreationOptions.AttachedToParent);
56 
57             // Set up some actions to take when task has completed.
58             task.ContinueWith(delegate
59             {
60                 switch (task.Status)
61                 {
62                     // If task did not run to completion, then record the cancellation/fault information
63                     // to tcs.Task.
64                     case TaskStatus.Canceled:
65                     case TaskStatus.Faulted:
66                         result = tcs.TrySetFromTask(task);
67                         Contract.Assert(result, "Unwrap(Task<Task>): Expected TrySetFromTask #1 to succeed");
68                         break;
69 
70                     case TaskStatus.RanToCompletion:
71                         // task.Result == null ==> proxy should be canceled.
72                         if (task.Result == null) tcs.TrySetCanceled();
73 
74                         // When task.Result completes, take some action to set the completion state of tcs.Task.
75                         else
76                         {
77                             task.Result.ContinueWith(_ =>
78                             {
79                                 // Copy completion/cancellation/exception info from task.Result to tcs.Task.
80                                 result = tcs.TrySetFromTask(task.Result);
81                                 Contract.Assert(result, "Unwrap(Task<Task>): Expected TrySetFromTask #2 to succeed");
82                             }, TaskContinuationOptions.ExecuteSynchronously).ContinueWith(antecedent =>
83                             {
84                                 // Clean up if ContinueWith() operation fails due to TSE
85                                 tcs.TrySetException(antecedent.Exception);
86                             }, TaskContinuationOptions.OnlyOnFaulted);
87                         }
88                         break;
89                 }
90             }, TaskContinuationOptions.ExecuteSynchronously).ContinueWith(antecedent =>
91             {
92                 // Clean up if ContinueWith() operation fails due to TSE
93                 tcs.TrySetException(antecedent.Exception);
94             }, TaskContinuationOptions.OnlyOnFaulted);
95 
96             // Return this immediately as a proxy.  When task.Result completes, or task is faulted/canceled,
97             // the completion information will be transfered to tcs.Task.
98             return tcs.Task;
99 
100 #else // Desktop or CoreSys
101             // Creates a proxy Task and hooks up the logic to have it represent the task.Result
102             Task promise = Task.CreateUnwrapPromise<VoidResult>(task, lookForOce : false);
103 
104             // Return the proxy immediately
105             return promise;
106 #endif
107         }
108 
109         /// <summary>
110         /// Creates a proxy <see cref="System.Threading.Tasks.Task{TResult}">Task{TResult}</see> that represents the
111         /// asynchronous operation of a Task{Task{TResult}}.
112         /// </summary>
113         /// <remarks>
114         /// It is often useful to be able to return a Task{TResult} from a Task{TResult}, where the inner Task{TResult}
115         /// represents work done as part of the outer Task{TResult}.  However, doing so results in a Task{Task{TResult}},
116         /// which, if not dealt with carefully, could produce unexpected behavior.  Unwrap solves this problem by
117         /// creating a proxy Task{TResult} that represents the entire asynchronous operation of such a Task{Task{TResult}}.
118         /// </remarks>
119         /// <param name="task">The Task{Task{TResult}} to unwrap.</param>
120         /// <exception cref="T:System.ArgumentNullException">The exception that is thrown if the
121         /// <paramref name="task"/> argument is null.</exception>
122         /// <returns>A Task{TResult} that represents the asynchronous operation of the provided Task{Task{TResult}}.</returns>
Unwrap(this Task<Task<TResult>> task)123         public static Task<TResult> Unwrap<TResult>(this Task<Task<TResult>> task)
124         {
125             if (task == null) throw new ArgumentNullException("task");
126 #if SILVERLIGHT && !FEATURE_NETCORE // CoreCLR only
127             bool result;
128 
129             // tcs.Task serves as a proxy for task.Result.
130             // AttachedToParent is the only legal option for TCS-style task.
131             var tcs = new TaskCompletionSource<TResult>(task.CreationOptions & TaskCreationOptions.AttachedToParent);
132 
133             // Set up some actions to take when task has completed.
134             task.ContinueWith(delegate
135             {
136                 switch (task.Status)
137                 {
138                     // If task did not run to completion, then record the cancellation/fault information
139                     // to tcs.Task.
140                     case TaskStatus.Canceled:
141                     case TaskStatus.Faulted:
142                         result = tcs.TrySetFromTask(task);
143                         Contract.Assert(result, "Unwrap(Task<Task<T>>): Expected TrySetFromTask #1 to succeed");
144                         break;
145 
146                     case TaskStatus.RanToCompletion:
147                         // task.Result == null ==> proxy should be canceled.
148                         if (task.Result == null) tcs.TrySetCanceled();
149 
150                         // When task.Result completes, take some action to set the completion state of tcs.Task.
151                         else
152                         {
153                             task.Result.ContinueWith(_ =>
154                             {
155                                 // Copy completion/cancellation/exception info from task.Result to tcs.Task.
156                                 result = tcs.TrySetFromTask(task.Result);
157                                 Contract.Assert(result, "Unwrap(Task<Task<T>>): Expected TrySetFromTask #2 to succeed");
158                             },
159                             TaskContinuationOptions.ExecuteSynchronously).ContinueWith(antecedent =>
160                             {
161                                 // Clean up if ContinueWith() operation fails due to TSE
162                                 tcs.TrySetException(antecedent.Exception);
163                             }, TaskContinuationOptions.OnlyOnFaulted);
164                         }
165 
166                         break;
167                 }
168             }, TaskContinuationOptions.ExecuteSynchronously).ContinueWith(antecedent =>
169             {
170                 // Clean up if ContinueWith() operation fails due to TSE
171                 tcs.TrySetException(antecedent.Exception);
172             }, TaskContinuationOptions.OnlyOnFaulted); ;
173 
174             // Return this immediately as a proxy.  When task.Result completes, or task is faulted/canceled,
175             // the completion information will be transfered to tcs.Task.
176             return tcs.Task;
177 
178 #else // Desktop or CoreSys
179             // Creates a proxy Task<TResult> and hooks up the logic to have it represent the task.Result
180             Task<TResult> promise = Task.CreateUnwrapPromise<TResult>(task, lookForOce : false);
181 
182             // Return the proxy immediately
183             return promise;
184 #endif
185         }
186 
187 #if SILVERLIGHT && !FEATURE_NETCORE // CoreCLR only
188         // Transfer the completion status from "source" to "me".
TrySetFromTask(this TaskCompletionSource<TResult> me, Task source)189         private static bool TrySetFromTask<TResult>(this TaskCompletionSource<TResult> me, Task source)
190         {
191             Contract.Assert(source.IsCompleted, "TrySetFromTask: Expected source to have completed.");
192             bool rval = false;
193 
194             switch(source.Status)
195             {
196                 case TaskStatus.Canceled:
197                     rval = me.TrySetCanceled();
198                     break;
199 
200                 case TaskStatus.Faulted:
201                     rval = me.TrySetException(source.Exception.InnerExceptions);
202                     break;
203 
204                 case TaskStatus.RanToCompletion:
205                     if(source is Task<TResult>)
206                         rval = me.TrySetResult( ((Task<TResult>)source).Result);
207                     else
208                         rval = me.TrySetResult(default(TResult));
209                     break;
210             }
211 
212             return rval;
213         }
214 #else // Desktop or CoreSys
215 
216         // Used as a placeholder TResult to indicate that a Task<TResult> has a void TResult
217         private struct VoidResult { }
218 
219 #endif
220 
221     }
222 }
223