#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