You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
11 KiB
220 lines
11 KiB
#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 |
|
{ |
|
/// <summary> |
|
/// Provides a set of static (Shared in Visual Basic) methods for working with specific kinds of |
|
/// <see cref="System.Threading.Tasks.Task"/> instances. |
|
/// </summary> |
|
public static class TaskExtensions |
|
{ |
|
/// <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"); |
|
} |
|
|
|
// 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<VoidResult>(task.CreationOptions & TaskCreationOptions.AttachedToParent); |
|
TransferAsynchronously(tcs, task); |
|
return tcs.Task; |
|
} |
|
|
|
/// <summary> |
|
/// Creates a proxy <see cref="System.Threading.Tasks.Task{TResult}">Task{TResult}</see> that represents the |
|
/// asynchronous operation of a Task{Task{TResult}}. |
|
/// </summary> |
|
/// <remarks> |
|
/// 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}}. |
|
/// </remarks> |
|
/// <param name="task">The Task{Task{TResult}} 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{TResult} that represents the asynchronous operation of the provided Task{Task{TResult}}.</returns> |
|
public static Task<TResult> Unwrap<TResult>(this Task<Task<TResult>> 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<TResult>(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<TResult>(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); |
|
} |
|
|
|
/// <summary> |
|
/// Transfer the results of the <paramref name="outer"/> task's inner task to the <paramref name="completionSource"/>. |
|
/// </summary> |
|
/// <param name="completionSource">The completion source to which results should be transfered.</param> |
|
/// <param name="outer"> |
|
/// The outer task that when completed will yield an inner task whose results we want marshaled to the <paramref name="completionSource"/>. |
|
/// </param> |
|
private static void TransferAsynchronously<TResult, TInner>(TaskCompletionSource<TResult> completionSource, Task<TInner> 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<TResult>(this TaskCompletionSource<TResult> 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<TResult>; |
|
result = resultTask != null ? |
|
completionSource.TrySetResult(resultTask.Result) : |
|
completionSource.TrySetResult(default(TResult)); |
|
break; |
|
} |
|
return result; |
|
} |
|
|
|
/// <summary>Dummy type to use as a void TResult.</summary> |
|
private struct VoidResult |
|
{ |
|
} |
|
} |
|
} |
|
|
|
#endif |