/// <summary>Creates a proxy <see cref="Task{TResult}"/> that represents the asynchronous operation of a <see cref="Task{Task{TResult}}"/>.</summary> /// <param name="task">The <see cref="Task{Task{TResult}}"/> to unwrap.</param> /// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation of the provided <see cref="Task{Task{TResult}}"/>.</returns> public static Task <TResult> Unwrap <TResult>(this Task <Task <TResult> > task) { if (task == null) { throw new ArgumentNullException(nameof(task)); } // If the task hasn't completed or was faulted/canceled, wrap it in an unwrap promise. Otherwise, // it completed successfully. Return its inner task to avoid unnecessary wrapping, or if the inner // task is null, return a canceled task to match the same semantics as CreateUnwrapPromise. return (!task.IsCompletedSuccessfully ? Task.CreateUnwrapPromise <TResult>(task, lookForOce: false) : task.Result ?? Task.FromCanceled <TResult>(new CancellationToken(true))); }
/// <summary> /// Creates a proxy <see cref="System.Threading.Tasks.Task">Task</see> that represents the /// asynchronous operation of a Task{Task}. /// </summary> /// <remarks> /// It is often useful to be able to return a Task from a <see cref="System.Threading.Tasks.Task{TResult}"> /// Task{TResult}</see>, where the inner Task represents work done as part of the outer Task{TResult}. However, /// doing so results in a Task{Task}, which, if not dealt with carefully, could produce unexpected behavior. Unwrap /// solves this problem by creating a proxy Task that represents the entire asynchronous operation of such a Task{Task}. /// </remarks> /// <param name="task">The Task{Task} to unwrap.</param> /// <exception cref="T:System.ArgumentNullException">The exception that is thrown if the /// <paramref name="task"/> argument is null.</exception> /// <returns>A Task that represents the asynchronous operation of the provided Task{Task}.</returns> public static Task Unwrap(this Task <Task> task) { if (task == null) { throw new ArgumentNullException("task"); } #if SILVERLIGHT && !FEATURE_NETCORE // CoreCLR only bool result; // tcs.Task serves as a proxy for task.Result. // AttachedToParent is the only legal option for TCS-style task. var tcs = new TaskCompletionSource <Task>(task.CreationOptions & TaskCreationOptions.AttachedToParent); // Set up some actions to take when task has completed. task.ContinueWith(delegate { switch (task.Status) { // If task did not run to completion, then record the cancellation/fault information // to tcs.Task. case TaskStatus.Canceled: case TaskStatus.Faulted: result = tcs.TrySetFromTask(task); Contract.Assert(result, "Unwrap(Task<Task>): Expected TrySetFromTask #1 to succeed"); break; case TaskStatus.RanToCompletion: // task.Result == null ==> proxy should be canceled. if (task.Result == null) { tcs.TrySetCanceled(); } // When task.Result completes, take some action to set the completion state of tcs.Task. else { task.Result.ContinueWith(_ => { // Copy completion/cancellation/exception info from task.Result to tcs.Task. result = tcs.TrySetFromTask(task.Result); Contract.Assert(result, "Unwrap(Task<Task>): Expected TrySetFromTask #2 to succeed"); }, TaskContinuationOptions.ExecuteSynchronously).ContinueWith(antecedent => { // Clean up if ContinueWith() operation fails due to TSE tcs.TrySetException(antecedent.Exception); }, TaskContinuationOptions.OnlyOnFaulted); } break; } }, TaskContinuationOptions.ExecuteSynchronously).ContinueWith(antecedent => { // Clean up if ContinueWith() operation fails due to TSE tcs.TrySetException(antecedent.Exception); }, TaskContinuationOptions.OnlyOnFaulted); // Return this immediately as a proxy. When task.Result completes, or task is faulted/canceled, // the completion information will be transfered to tcs.Task. return(tcs.Task); #else // Desktop or CoreSys // Creates a proxy Task and hooks up the logic to have it represent the task.Result Task promise = Task.CreateUnwrapPromise <VoidResult>(task, lookForOce: false); // Return the proxy immediately return(promise); #endif }