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