#if NET20 || NET30 || NET35 || !NET_4_6 // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Diagnostics; using LinqInternal.Core; namespace System.Threading.Tasks { /// /// Provides a set of static (Shared in Visual Basic) methods for working with specific kinds of /// instances. /// public static class TaskExtensions { /// /// Creates a proxy Task that represents the /// asynchronous operation of a Task{Task}. /// /// /// It is often useful to be able to return a Task from a /// Task{TResult}, 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}. /// /// The Task{Task} to unwrap. /// The exception that is thrown if the /// argument is null. /// A Task that represents the asynchronous operation of the provided Task{Task}. public static Task Unwrap(this Task task) { if (task == null) { throw new ArgumentNullException("task"); } // Fast path for an already successfully completed outer task: just return the inner one. // As in the subsequent slower path, a null inner task is special-cased to mean cancellation. if (task.Status == TaskStatus.RanToCompletion && (task.CreationOptions & TaskCreationOptions.AttachedToParent) == 0) { return task.Result ?? Task.FromCanceled(new CancellationToken(true)); } // Create a new Task to serve as a proxy for the actual inner task. Attach it // to the parent if the original was attached to the parent. var tcs = new TaskCompletionSource(task.CreationOptions & TaskCreationOptions.AttachedToParent); TransferAsynchronously(tcs, task); return tcs.Task; } /// /// Creates a proxy Task{TResult} that represents the /// asynchronous operation of a Task{Task{TResult}}. /// /// /// It is often useful to be able to return a Task{TResult} from a Task{TResult}, where the inner Task{TResult} /// represents work done as part of the outer Task{TResult}. However, doing so results in a Task{Task{TResult}}, /// which, if not dealt with carefully, could produce unexpected behavior. Unwrap solves this problem by /// creating a proxy Task{TResult} that represents the entire asynchronous operation of such a Task{Task{TResult}}. /// /// The Task{Task{TResult}} to unwrap. /// The exception that is thrown if the /// argument is null. /// A Task{TResult} that represents the asynchronous operation of the provided Task{Task{TResult}}. public static Task Unwrap(this Task> task) { if (task == null) { throw new ArgumentNullException("task"); } // Fast path for an already successfully completed outer task: just return the inner one. // As in the subsequent slower path, a null inner task is special-cased to mean cancellation. if (task.Status == TaskStatus.RanToCompletion && (task.CreationOptions & TaskCreationOptions.AttachedToParent) == 0) { return task.Result ?? Task.FromCanceled(new CancellationToken(true)); } // Create a new Task to serve as a proxy for the actual inner task. Attach it // to the parent if the original was attached to the parent. var tcs = new TaskCompletionSource(task.CreationOptions & TaskCreationOptions.AttachedToParent); TransferAsynchronously(tcs, task); return tcs.Task; } private static CancellationToken ExtractCancellationToken(Task task) { // With the public Task APIs as of .NET 4.6, the only way to extract a CancellationToken // that was associated with a Task is by await'ing it, catching the resulting // OperationCanceledException, and getting the token from the OCE. Debug.Assert(task.IsCanceled); try { task.GetAwaiter().GetResult(); } catch (NewOperationCanceledException oce) { var ct = oce.CancellationToken; if (ct.IsCancellationRequested) { return ct; } } return new CancellationToken(true); } /// /// Transfer the results of the task's inner task to the . /// /// The completion source to which results should be transfered. /// /// The outer task that when completed will yield an inner task whose results we want marshaled to the . /// private static void TransferAsynchronously(TaskCompletionSource completionSource, Task outer) where TInner : Task { Action[] callback = { null }; // Create a continuation delegate. For performance reasons, we reuse the same delegate/closure across multiple // continuations; by using .ConfigureAwait(false).GetAwaiter().UnsafeOnComplete(action), in most cases // this delegate can be stored directly into the Task's continuation field, eliminating the need for additional // allocations. Thus, this whole Unwrap operation generally results in four allocations: one for the TaskCompletionSource, // one for the returned task, one for the delegate, and one for the closure. Since the delegate is used // across multiple continuations, we use the callback variable as well to indicate which continuation we're in: // if the callback is non-null, then we're processing the continuation for the outer task and use the callback // object as the continuation off of the inner task; if the callback is null, then we're processing the // inner task. callback[0] = delegate { Debug.Assert(outer.IsCompleted); if (callback[0] != null) { // Process the outer task's completion // Clear out the callback field to indicate that any future invocations should // be for processing the inner task, but store away a local copy in case we need // to use it as the continuation off of the outer task. var innerCallback = callback[0]; callback[0] = null; var result = true; switch (outer.Status) { case TaskStatus.Canceled: case TaskStatus.Faulted: // The outer task has completed as canceled or faulted; transfer that // status to the completion source, and we're done. result = completionSource.TrySetFromTask(outer); break; case TaskStatus.RanToCompletion: Task inner = outer.Result; if (inner == null) { // The outer task completed successfully, but with a null inner task; // cancel the completionSource, and we're done. result = completionSource.TrySetCanceled(); } else if (inner.IsCompleted) { // The inner task also completed! Transfer the results, and we're done. result = completionSource.TrySetFromTask(inner); } else { // Run this delegate again once the inner task has completed. inner.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(innerCallback); } break; } Debug.Assert(result); } else { // Process the inner task's completion. All we need to do is transfer its results // to the completion source. Debug.Assert(outer.Status == TaskStatus.RanToCompletion); Debug.Assert(outer.Result.IsCompleted); completionSource.TrySetFromTask(outer.Result); } }; // Kick things off by hooking up the callback as the task's continuation outer.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(callback[0]); } private static bool TrySetFromTask(this TaskCompletionSource completionSource, Task task) { Debug.Assert(task.IsCompleted); // Transfer the results from the supplied Task to the TaskCompletionSource. var result = false; switch (task.Status) { case TaskStatus.Canceled: result = completionSource.TrySetCanceled(ExtractCancellationToken(task)); break; case TaskStatus.Faulted: result = completionSource.TrySetException(task.Exception.InnerExceptions); break; case TaskStatus.RanToCompletion: var resultTask = task as Task; result = resultTask != null ? completionSource.TrySetResult(resultTask.Result) : completionSource.TrySetResult(default(TResult)); break; } return result; } /// Dummy type to use as a void TResult. private struct VoidResult { } } } #endif