// 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; } } } }