// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
namespace System.Threading.Tasks
{
///
/// Helpers for safely using Task libraries.
///
internal static class TaskHelpers
{
private static Task _defaultCompleted = FromResult(default(AsyncVoid));
///
/// Returns a canceled Task. The task is completed, IsCanceled = True, IsFaulted = False.
///
internal static Task Canceled()
{
return CancelCache.Canceled;
}
///
/// Returns a canceled Task of the given type. The task is completed, IsCanceled = True, IsFaulted = False.
///
internal static Task Canceled()
{
return CancelCache.Canceled;
}
///
/// Returns a completed task that has no result.
///
internal static Task Completed()
{
return _defaultCompleted;
}
///
/// Returns an error task. The task is Completed, IsCanceled = False, IsFaulted = True
///
internal static Task FromError(Exception exception)
{
return FromError(exception);
}
///
/// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True
///
///
internal static Task FromError(Exception exception)
{
TaskCompletionSource tcs = new TaskCompletionSource();
tcs.SetException(exception);
return tcs.Task;
}
///
/// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True
///
internal static Task FromErrors(IEnumerable exceptions)
{
return FromErrors(exceptions);
}
///
/// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True
///
internal static Task FromErrors(IEnumerable exceptions)
{
TaskCompletionSource tcs = new TaskCompletionSource();
tcs.SetException(exceptions);
return tcs.Task;
}
///
/// Returns a successful completed task with the given result.
///
internal static Task FromResult(TResult result)
{
TaskCompletionSource tcs = new TaskCompletionSource();
tcs.SetResult(result);
return tcs.Task;
}
///
/// Return a task that runs all the tasks inside the iterator sequentially. It stops as soon
/// as one of the tasks fails or cancels, or after all the tasks have run succesfully.
///
/// collection of tasks to wait on
/// cancellation token
/// a task that signals completed when all the incoming tasks are finished.
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is propagated in a Task.")]
internal static Task Iterate(IEnumerable asyncIterator, CancellationToken cancellationToken = default(CancellationToken))
{
Contract.Assert(asyncIterator != null);
IEnumerator enumerator = null;
try
{
enumerator = asyncIterator.GetEnumerator();
Task task = IterateImpl(enumerator, cancellationToken);
return (enumerator != null) ? task.Finally(enumerator.Dispose) : task;
}
catch (Exception ex)
{
return TaskHelpers.FromError(ex);
}
}
///
/// Provides the implementation of the Iterate method.
/// Contains special logic to help speed up common cases.
///
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is propagated in a Task.")]
internal static Task IterateImpl(IEnumerator enumerator, CancellationToken cancellationToken)
{
try
{
while (true)
{
// short-circuit: iteration canceled
if (cancellationToken.IsCancellationRequested)
{
return TaskHelpers.Canceled();
}
// short-circuit: iteration complete
if (!enumerator.MoveNext())
{
return TaskHelpers.Completed();
}
// fast case: Task completed synchronously & successfully
Task currentTask = enumerator.Current;
if (currentTask.Status == TaskStatus.RanToCompletion)
{
continue;
}
// fast case: Task completed synchronously & unsuccessfully
if (currentTask.IsCanceled || currentTask.IsFaulted)
{
return currentTask;
}
// slow case: Task isn't yet complete
return IterateImplIncompleteTask(enumerator, currentTask, cancellationToken);
}
}
catch (Exception ex)
{
return TaskHelpers.FromError(ex);
}
}
///
/// Fallback for IterateImpl when the antecedent Task isn't yet complete.
///
internal static Task IterateImplIncompleteTask(IEnumerator enumerator, Task currentTask, CancellationToken cancellationToken)
{
// There's a race condition here, the antecedent Task could complete between
// the check in Iterate and the call to Then below. If this happens, we could
// end up growing the stack indefinitely. But the chances of (a) even having
// enough Tasks in the enumerator in the first place and of (b) *every* one
// of them hitting this race condition are so extremely remote that it's not
// worth worrying about.
return currentTask.Then(() => IterateImpl(enumerator, cancellationToken));
}
///
/// Replacement for Task.Factory.StartNew when the code can run synchronously.
/// We run the code immediately and avoid the thread switch.
/// This is used to help synchronous code implement task interfaces.
///
/// action to run synchronouslyt
/// cancellation token. This is only checked before we run the task, and if cancelled, we immediately return a cancelled task.
/// a task who result is the result from Func()
///
/// Avoid calling Task.Factory.StartNew.
/// This avoids gotchas with StartNew:
/// - ensures cancellation token is checked (StartNew doesn't check cancellation tokens).
/// - Keeps on the same thread.
/// - Avoids switching synchronization contexts.
/// Also take in a lambda so that we can wrap in a try catch and honor task failure semantics.
///
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
public static Task RunSynchronously(Action action, CancellationToken token = default(CancellationToken))
{
if (token.IsCancellationRequested)
{
return Canceled();
}
try
{
action();
return Completed();
}
catch (Exception e)
{
return FromError(e);
}
}
///
/// Replacement for Task.Factory.StartNew when the code can run synchronously.
/// We run the code immediately and avoid the thread switch.
/// This is used to help synchronous code implement task interfaces.
///
/// type of result that task will return.
/// function to run synchronously and produce result
/// cancellation token. This is only checked before we run the task, and if cancelled, we immediately return a cancelled task.
/// a task who result is the result from Func()
///
/// Avoid calling Task.Factory.StartNew.
/// This avoids gotchas with StartNew:
/// - ensures cancellation token is checked (StartNew doesn't check cancellation tokens).
/// - Keeps on the same thread.
/// - Avoids switching synchronization contexts.
/// Also take in a lambda so that we can wrap in a try catch and honor task failure semantics.
///
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
internal static Task RunSynchronously(Func func, CancellationToken cancellationToken = default(CancellationToken))
{
if (cancellationToken.IsCancellationRequested)
{
return Canceled();
}
try
{
return FromResult(func());
}
catch (Exception e)
{
return FromError(e);
}
}
///
/// Overload of RunSynchronously that avoids a call to Unwrap().
/// This overload is useful when func() starts doing some synchronous work and then hits IO and
/// needs to create a task to finish the work.
///
/// type of result that Task will return
/// function that returns a task
/// cancellation token. This is only checked before we run the task, and if cancelled, we immediately return a cancelled task.
/// a task, created by running func().
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
internal static Task RunSynchronously(Func> func, CancellationToken cancellationToken = default(CancellationToken))
{
if (cancellationToken.IsCancellationRequested)
{
return Canceled();
}
try
{
return func();
}
catch (Exception e)
{
return FromError(e);
}
}
///
/// Update the completion source if the task failed (cancelled or faulted). No change to completion source if the task succeeded.
///
/// result type of completion source
/// completion source to update
/// task to update from.
/// true on success
internal static bool SetIfTaskFailed(this TaskCompletionSource tcs, Task source)
{
switch (source.Status)
{
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return tcs.TrySetFromTask(source);
}
return false;
}
///
/// Set a completion source from the given Task.
///
/// result type for completion source.
/// completion source to set
/// Task to get values from.
/// true if this successfully sets the completion source.
[SuppressMessage("Microsoft.WebAPI", "CR4001:DoNotCallProblematicMethodsOnTask", Justification = "This is a known safe usage of Task.Result, since it only occurs when we know the task's state to be completed.")]
internal static bool TrySetFromTask(this TaskCompletionSource tcs, Task source)
{
if (source.Status == TaskStatus.Canceled)
{
return tcs.TrySetCanceled();
}
if (source.Status == TaskStatus.Faulted)
{
return tcs.TrySetException(source.Exception.InnerExceptions);
}
if (source.Status == TaskStatus.RanToCompletion)
{
Task taskOfResult = source as Task;
return tcs.TrySetResult(taskOfResult == null ? default(TResult) : taskOfResult.Result);
}
return false;
}
///
/// Set a completion source from the given Task. If the task ran to completion and the result type doesn't match
/// the type of the completion source, then a default value will be used. This is useful for converting Task into
/// Task{AsyncVoid}, but it can also accidentally be used to introduce data loss (by passing the wrong
/// task type), so please execute this method with care.
///
/// result type for completion source.
/// completion source to set
/// Task to get values from.
/// true if this successfully sets the completion source.
[SuppressMessage("Microsoft.WebAPI", "CR4001:DoNotCallProblematicMethodsOnTask", Justification = "This is a known safe usage of Task.Result, since it only occurs when we know the task's state to be completed.")]
internal static bool TrySetFromTask(this TaskCompletionSource> tcs, Task source)
{
if (source.Status == TaskStatus.Canceled)
{
return tcs.TrySetCanceled();
}
if (source.Status == TaskStatus.Faulted)
{
return tcs.TrySetException(source.Exception.InnerExceptions);
}
if (source.Status == TaskStatus.RanToCompletion)
{
// Sometimes the source task is Task>, and sometimes it's Task.
// The latter usually happens when we're in the middle of a sync-block postback where
// the continuation is a function which returns Task rather than just TResult,
// but the originating task was itself just Task. An example of this can be
// found in TaskExtensions.CatchImpl().
Task> taskOfTaskOfResult = source as Task>;
if (taskOfTaskOfResult != null)
{
return tcs.TrySetResult(taskOfTaskOfResult.Result);
}
Task taskOfResult = source as Task;
if (taskOfResult != null)
{
return tcs.TrySetResult(taskOfResult);
}
return tcs.TrySetResult(TaskHelpers.FromResult(default(TResult)));
}
return false;
}
///
/// Used as the T in a "conversion" of a Task into a Task{T}
///
private struct AsyncVoid
{
}
///
/// This class is a convenient cache for per-type cancelled tasks
///
private static class CancelCache
{
public static readonly Task Canceled = GetCancelledTask();
private static Task GetCancelledTask()
{
TaskCompletionSource tcs = new TaskCompletionSource();
tcs.SetCanceled();
return tcs.Task;
}
}
}
}