Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Extensions for Task Parallel Library from ASP.NET MVC project.
/*
Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
Microsoft Open Technologies would like to thank its contributors, a list
of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors.
Licensed under the Apache License, Version 2.0 (the "License"); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing permissions
and limitations under the License.
*/
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
namespace System.Threading.Tasks
{
/// <summary>
/// Helpers for safely using Task libraries.
/// </summary>
public static class TaskHelpers
{
private static readonly Task _defaultCompleted = FromResult<AsyncVoid>(default(AsyncVoid));
private static readonly Task<object> _completedTaskReturningNull = FromResult<object>(null);
/// <summary>
/// Returns a canceled Task. The task is completed, IsCanceled = True, IsFaulted = False.
/// </summary>
public static Task Canceled()
{
return CancelCache<AsyncVoid>.Canceled;
}
/// <summary>
/// Returns a canceled Task of the given type. The task is completed, IsCanceled = True, IsFaulted = False.
/// </summary>
public static Task<TResult> Canceled<TResult>()
{
return CancelCache<TResult>.Canceled;
}
/// <summary>
/// Returns a completed task that has no result.
/// </summary>
public static Task Completed()
{
return _defaultCompleted;
}
/// <summary>
/// Returns an error task. The task is Completed, IsCanceled = False, IsFaulted = True
/// </summary>
public static Task FromError(Exception exception)
{
return FromError<AsyncVoid>(exception);
}
/// <summary>
/// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True
/// </summary>
/// <typeparam name="TResult"></typeparam>
public static Task<TResult> FromError<TResult>(Exception exception)
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
tcs.SetException(exception);
return tcs.Task;
}
/// <summary>
/// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True
/// </summary>
public static Task FromErrors(IEnumerable<Exception> exceptions)
{
return FromErrors<AsyncVoid>(exceptions);
}
/// <summary>
/// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True
/// </summary>
public static Task<TResult> FromErrors<TResult>(IEnumerable<Exception> exceptions)
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
tcs.SetException(exceptions);
return tcs.Task;
}
/// <summary>
/// Returns a successful completed task with the given result.
/// </summary>
public static Task<TResult> FromResult<TResult>(TResult result)
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
tcs.SetResult(result);
return tcs.Task;
}
public static Task<object> NullResult()
{
return _completedTaskReturningNull;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="asyncIterator">collection of tasks to wait on</param>
/// <param name="cancellationToken">cancellation token</param>
/// <param name="disposeEnumerator">whether or not to dispose the enumerator we get from <paramref name="asyncIterator"/>.
/// Only set to <c>false</c> if you can guarantee that <paramref name="asyncIterator"/>'s enumerator does not have any resources it needs to dispose.</param>
/// <returns>a task that signals completed when all the incoming tasks are finished.</returns>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is propagated in a Task.")]
public static Task Iterate(IEnumerable<Task> asyncIterator, CancellationToken cancellationToken = default(CancellationToken), bool disposeEnumerator = true)
{
Contract.Assert(asyncIterator != null);
IEnumerator<Task> enumerator = null;
try
{
enumerator = asyncIterator.GetEnumerator();
Task task = IterateImpl(enumerator, cancellationToken);
return (disposeEnumerator && enumerator != null) ? task.Finally(enumerator.Dispose, runSynchronously: true) : task;
}
catch (Exception ex)
{
return TaskHelpers.FromError(ex);
}
}
/// <summary>
/// Provides the implementation of the Iterate method.
/// Contains special logic to help speed up common cases.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is propagated in a Task.")]
public static Task IterateImpl(IEnumerator<Task> 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);
}
}
/// <summary>
/// Fallback for IterateImpl when the antecedent Task isn't yet complete.
/// </summary>
public static Task IterateImplIncompleteTask(IEnumerator<Task> 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));
}
/// <summary>
/// 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.
/// </summary>
/// <param name="action">action to run synchronouslyt</param>
/// <param name="token">cancellation token. This is only checked before we run the task, and if cancelled, we immediately return a cancelled task.</param>
/// <returns>a task who result is the result from Func()</returns>
/// <remarks>
/// 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.
/// </remarks>
[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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TResult">type of result that task will return.</typeparam>
/// <param name="func">function to run synchronously and produce result</param>
/// <param name="cancellationToken">cancellation token. This is only checked before we run the task, and if cancelled, we immediately return a cancelled task.</param>
/// <returns>a task who result is the result from Func()</returns>
/// <remarks>
/// 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.
/// </remarks>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
public static Task<TResult> RunSynchronously<TResult>(Func<TResult> func, CancellationToken cancellationToken = default(CancellationToken))
{
if (cancellationToken.IsCancellationRequested)
{
return Canceled<TResult>();
}
try
{
return FromResult(func());
}
catch (Exception e)
{
return FromError<TResult>(e);
}
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TResult">type of result that Task will return</typeparam>
/// <param name="func">function that returns a task</param>
/// <param name="cancellationToken">cancellation token. This is only checked before we run the task, and if cancelled, we immediately return a cancelled task.</param>
/// <returns>a task, created by running func().</returns>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
public static Task<TResult> RunSynchronously<TResult>(Func<Task<TResult>> func, CancellationToken cancellationToken = default(CancellationToken))
{
if (cancellationToken.IsCancellationRequested)
{
return Canceled<TResult>();
}
try
{
return func();
}
catch (Exception e)
{
return FromError<TResult>(e);
}
}
/// <summary>
/// Update the completion source if the task failed (cancelled or faulted). No change to completion source if the task succeeded.
/// </summary>
/// <typeparam name="TResult">result type of completion source</typeparam>
/// <param name="tcs">completion source to update</param>
/// <param name="source">task to update from.</param>
/// <returns>true on success</returns>
public static bool SetIfTaskFailed<TResult>(this TaskCompletionSource<TResult> tcs, Task source)
{
switch (source.Status)
{
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return tcs.TrySetFromTask(source);
}
return false;
}
/// <summary>
/// Set a completion source from the given Task.
/// </summary>
/// <typeparam name="TResult">result type for completion source.</typeparam>
/// <param name="tcs">completion source to set</param>
/// <param name="source">Task to get values from.</param>
/// <returns>true if this successfully sets the completion source.</returns>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201: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.")]
public static bool TrySetFromTask<TResult>(this TaskCompletionSource<TResult> 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<TResult> taskOfResult = source as Task<TResult>;
return tcs.TrySetResult(taskOfResult == null ? default(TResult) : taskOfResult.Result);
}
return false;
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TResult">result type for completion source.</typeparam>
/// <param name="tcs">completion source to set</param>
/// <param name="source">Task to get values from.</param>
/// <returns>true if this successfully sets the completion source.</returns>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201: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.")]
public static bool TrySetFromTask<TResult>(this TaskCompletionSource<Task<TResult>> 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<Task<TResult>>, and sometimes it's Task<TResult>.
// The latter usually happens when we're in the middle of a sync-block postback where
// the continuation is a function which returns Task<TResult> rather than just TResult,
// but the originating task was itself just Task<TResult>. An example of this can be
// found in TaskExtensions.CatchImpl().
Task<Task<TResult>> taskOfTaskOfResult = source as Task<Task<TResult>>;
if (taskOfTaskOfResult != null)
{
return tcs.TrySetResult(taskOfTaskOfResult.Result);
}
Task<TResult> taskOfResult = source as Task<TResult>;
if (taskOfResult != null)
{
return tcs.TrySetResult(taskOfResult);
}
return tcs.TrySetResult(TaskHelpers.FromResult(default(TResult)));
}
return false;
}
/// <summary>
/// Used as the T in a "conversion" of a Task into a Task{T}
/// </summary>
private struct AsyncVoid
{
}
/// <summary>
/// This class is a convenient cache for per-type cancelled tasks
/// </summary>
private static class CancelCache<TResult>
{
public static readonly Task<TResult> Canceled = GetCancelledTask();
private static Task<TResult> GetCancelledTask()
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
tcs.SetCanceled();
return tcs.Task;
}
}
}
}
/*
Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
Microsoft Open Technologies would like to thank its contributors, a list
of whom are at http://aspnetwebstack.codeplex.com/wikipage?title=Contributors.
Licensed under the Apache License, Version 2.0 (the "License"); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing permissions
and limitations under the License.
*/
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Reflection;
namespace System.Threading.Tasks
{
public static class TaskHelpersExtensions
{
private static Task<AsyncVoid> _defaultCompleted = TaskHelpers.FromResult<AsyncVoid>(default(AsyncVoid));
private static readonly Action<Task> _rethrowWithNoStackLossDelegate = GetRethrowWithNoStackLossDelegate();
/// <summary>
/// Calls the given continuation, after the given task completes, if it ends in a faulted state.
/// Will not be called if the task did not fault (meaning, it will not be called if the task ran
/// to completion or was canceled). Intended to roughly emulate C# 5's support for "try/catch" in
/// async methods. Note that this method allows you to return a Task, so that you can either return
/// a completed Task (indicating that you swallowed the exception) or a faulted task (indicating that
/// that the exception should be propagated). In C#, you cannot normally use await within a catch
/// block, so returning a real async task should never be done from Catch().
/// </summary>
public static Task Catch(this Task task, Func<CatchInfo, CatchInfo.CatchResult> continuation, CancellationToken cancellationToken = default(CancellationToken))
{
// Fast path for successful tasks, to prevent an extra TCS allocation
if (task.Status == TaskStatus.RanToCompletion)
{
return task;
}
return task.CatchImpl(() => continuation(new CatchInfo(task)).Task.ToTask<AsyncVoid>(), cancellationToken);
}
/// <summary>
/// Calls the given continuation, after the given task completes, if it ends in a faulted state.
/// Will not be called if the task did not fault (meaning, it will not be called if the task ran
/// to completion or was canceled). Intended to roughly emulate C# 5's support for "try/catch" in
/// async methods. Note that this method allows you to return a Task, so that you can either return
/// a completed Task (indicating that you swallowed the exception) or a faulted task (indicating that
/// that the exception should be propagated). In C#, you cannot normally use await within a catch
/// block, so returning a real async task should never be done from Catch().
/// </summary>
public static Task<TResult> Catch<TResult>(this Task<TResult> task, Func<CatchInfo<TResult>, CatchInfo<TResult>.CatchResult> continuation, CancellationToken cancellationToken = default(CancellationToken))
{
// Fast path for successful tasks, to prevent an extra TCS allocation
if (task.Status == TaskStatus.RanToCompletion)
{
return task;
}
return task.CatchImpl(() => continuation(new CatchInfo<TResult>(task)).Task, cancellationToken);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "CatchInfo", Justification = "This is the name of a class.")]
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "TaskHelpersExtensions", Justification = "This is the name of a class.")]
private static Task<TResult> CatchImpl<TResult>(this Task task, Func<Task<TResult>> continuation, CancellationToken cancellationToken)
{
// Stay on the same thread if we can
if (task.IsCompleted)
{
if (task.IsFaulted)
{
try
{
Task<TResult> resultTask = continuation();
if (resultTask == null)
{
// Not a resource because this is an internal class, and this is a guard clause that's intended
// to be thrown by us to us, never escaping out to end users.
throw new InvalidOperationException("You must set the Task property of the CatchInfo returned from the TaskHelpersExtensions.Catch continuation.");
}
return resultTask;
}
catch (Exception ex)
{
return TaskHelpers.FromError<TResult>(ex);
}
}
if (task.IsCanceled || cancellationToken.IsCancellationRequested)
{
return TaskHelpers.Canceled<TResult>();
}
if (task.Status == TaskStatus.RanToCompletion)
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
tcs.TrySetFromTask(task);
return tcs.Task;
}
}
// Split into a continuation method so that we don't create a closure unnecessarily
return CatchImplContinuation(task, continuation);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "TaskHelpersExtensions", Justification = "This is the name of a class.")]
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
private static Task<TResult> CatchImplContinuation<TResult>(Task task, Func<Task<TResult>> continuation)
{
SynchronizationContext syncContext = SynchronizationContext.Current;
TaskCompletionSource<Task<TResult>> tcs = new TaskCompletionSource<Task<TResult>>();
// this runs only if the inner task did not fault
task.ContinueWith(innerTask => tcs.TrySetFromTask(innerTask), TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
// this runs only if the inner task faulted
task.ContinueWith(innerTask =>
{
if (syncContext != null)
{
syncContext.Post(state =>
{
try
{
Task<TResult> resultTask = continuation();
if (resultTask == null)
{
throw new InvalidOperationException("You cannot return null from the TaskHelpersExtensions.Catch continuation. You must return a valid task or throw an exception.");
}
tcs.TrySetResult(resultTask);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}, state: null);
}
else
{
try
{
Task<TResult> resultTask = continuation();
if (resultTask == null)
{
throw new InvalidOperationException("You cannot return null from the TaskHelpersExtensions.Catch continuation. You must return a valid task or throw an exception.");
}
tcs.TrySetResult(resultTask);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
}, TaskContinuationOptions.OnlyOnFaulted);
return tcs.Task.FastUnwrap();
}
/// <summary>
/// Upon completion of the task, copies its result into the given task completion source, regardless of the
/// completion state. This causes the original task to be fully observed, and the task that is returned by
/// this method will always successfully run to completion, regardless of the original task state.
/// Since this method consumes a task with no return value, you must provide the return value to be used
/// when the inner task ran to successful completion.
/// </summary>
public static Task CopyResultToCompletionSource<TResult>(this Task task, TaskCompletionSource<TResult> tcs, TResult completionResult)
{
return task.CopyResultToCompletionSourceImpl(tcs, innerTask => completionResult);
}
/// <summary>
/// Upon completion of the task, copies its result into the given task completion source, regardless of the
/// completion state. This causes the original task to be fully observed, and the task that is returned by
/// this method will always successfully run to completion, regardless of the original task state.
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task CopyResultToCompletionSource<TResult>(this Task<TResult> task, TaskCompletionSource<TResult> tcs)
{
return task.CopyResultToCompletionSourceImpl(tcs, innerTask => innerTask.Result);
}
private static Task CopyResultToCompletionSourceImpl<TTask, TResult>(this TTask task, TaskCompletionSource<TResult> tcs, Func<TTask, TResult> resultThunk)
where TTask : Task
{
// Stay on the same thread if we can
if (task.IsCompleted)
{
switch (task.Status)
{
case TaskStatus.Canceled:
case TaskStatus.Faulted:
TaskHelpers.TrySetFromTask(tcs, task);
break;
case TaskStatus.RanToCompletion:
tcs.TrySetResult(resultThunk(task));
break;
}
return TaskHelpers.Completed();
}
// Split into a continuation method so that we don't create a closure unnecessarily
return CopyResultToCompletionSourceImplContinuation(task, tcs, resultThunk);
}
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
private static Task CopyResultToCompletionSourceImplContinuation<TTask, TResult>(TTask task, TaskCompletionSource<TResult> tcs, Func<TTask, TResult> resultThunk)
where TTask : Task
{
return task.ContinueWith(innerTask =>
{
switch (innerTask.Status)
{
case TaskStatus.Canceled:
case TaskStatus.Faulted:
TaskHelpers.TrySetFromTask(tcs, innerTask);
break;
case TaskStatus.RanToCompletion:
tcs.TrySetResult(resultThunk(task));
break;
}
}, TaskContinuationOptions.ExecuteSynchronously);
}
/// <summary>
/// Cast Task to Task of object
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task<object> CastToObject(this Task task)
{
// Stay on the same thread if we can
if (task.IsCompleted)
{
if (task.IsFaulted)
{
return TaskHelpers.FromErrors<object>(task.Exception.InnerExceptions);
}
if (task.IsCanceled)
{
return TaskHelpers.Canceled<object>();
}
if (task.Status == TaskStatus.RanToCompletion)
{
return TaskHelpers.FromResult<object>((object)null);
}
}
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
// schedule a synchronous task to cast: no need to worry about sync context or try/catch
task.ContinueWith(innerTask =>
{
if (innerTask.IsFaulted)
{
tcs.SetException(innerTask.Exception.InnerExceptions);
}
else if (innerTask.IsCanceled)
{
tcs.SetCanceled();
}
else
{
tcs.SetResult((object)null);
}
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
/// <summary>
/// Cast Task of T to Task of object
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task<object> CastToObject<T>(this Task<T> task)
{
// Stay on the same thread if we can
if (task.IsCompleted)
{
if (task.IsFaulted)
{
return TaskHelpers.FromErrors<object>(task.Exception.InnerExceptions);
}
if (task.IsCanceled)
{
return TaskHelpers.Canceled<object>();
}
if (task.Status == TaskStatus.RanToCompletion)
{
return TaskHelpers.FromResult<object>((object)task.Result);
}
}
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
// schedule a synchronous task to cast: no need to worry about sync context or try/catch
task.ContinueWith(innerTask =>
{
if (innerTask.IsFaulted)
{
tcs.SetException(innerTask.Exception.InnerExceptions);
}
else if (innerTask.IsCanceled)
{
tcs.SetCanceled();
}
else
{
tcs.SetResult((object)innerTask.Result);
}
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
/// <summary>
/// Cast Task of object to Task of T
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task<TOuterResult> CastFromObject<TOuterResult>(this Task<object> task)
{
// Stay on the same thread if we can
if (task.IsCompleted)
{
if (task.IsFaulted)
{
return TaskHelpers.FromErrors<TOuterResult>(task.Exception.InnerExceptions);
}
if (task.IsCanceled)
{
return TaskHelpers.Canceled<TOuterResult>();
}
if (task.Status == TaskStatus.RanToCompletion)
{
try
{
return TaskHelpers.FromResult<TOuterResult>((TOuterResult)task.Result);
}
catch (Exception exception)
{
return TaskHelpers.FromError<TOuterResult>(exception);
}
}
}
TaskCompletionSource<TOuterResult> tcs = new TaskCompletionSource<TOuterResult>();
// schedule a synchronous task to cast: no need to worry about sync context or try/catch
task.ContinueWith(innerTask =>
{
if (innerTask.IsFaulted)
{
tcs.SetException(innerTask.Exception.InnerExceptions);
}
else if (innerTask.IsCanceled)
{
tcs.SetCanceled();
}
else
{
try
{
tcs.SetResult((TOuterResult)innerTask.Result);
}
catch (Exception exception)
{
tcs.SetException(exception);
}
}
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
/// <summary>
/// A version of task.Unwrap that is optimized to prevent unnecessarily capturing the
/// execution context when the antecedent task is already completed.
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1202:DoNotUseProblematicTaskTypes", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task FastUnwrap(this Task<Task> task)
{
Task innerTask = task.Status == TaskStatus.RanToCompletion ? task.Result : null;
return innerTask ?? task.Unwrap();
}
/// <summary>
/// A version of task.Unwrap that is optimized to prevent unnecessarily capturing the
/// execution context when the antecedent task is already completed.
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1202:DoNotUseProblematicTaskTypes", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task<TResult> FastUnwrap<TResult>(this Task<Task<TResult>> task)
{
Task<TResult> innerTask = task.Status == TaskStatus.RanToCompletion ? task.Result : null;
return innerTask ?? task.Unwrap();
}
/// <summary>
/// Calls the given continuation, after the given task has completed, regardless of the state
/// the task ended in. Intended to roughly emulate C# 5's support for "finally" in async methods.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
public static Task Finally(this Task task, Action continuation, bool runSynchronously = false)
{
// Stay on the same thread if we can
if (task.IsCompleted)
{
try
{
continuation();
return task;
}
catch (Exception ex)
{
MarkExceptionsObserved(task);
return TaskHelpers.FromError(ex);
}
}
// Split into a continuation method so that we don't create a closure unnecessarily
return FinallyImplContinuation<AsyncVoid>(task, continuation, runSynchronously);
}
/// <summary>
/// Calls the given continuation, after the given task has completed, regardless of the state
/// the task ended in. Intended to roughly emulate C# 5's support for "finally" in async methods.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
public static Task<TResult> Finally<TResult>(this Task<TResult> task, Action continuation, bool runSynchronously = false)
{
// Stay on the same thread if we can
if (task.IsCompleted)
{
try
{
continuation();
return task;
}
catch (Exception ex)
{
MarkExceptionsObserved(task);
return TaskHelpers.FromError<TResult>(ex);
}
}
// Split into a continuation method so that we don't create a closure unnecessarily
return FinallyImplContinuation<TResult>(task, continuation, runSynchronously);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
private static Task<TResult> FinallyImplContinuation<TResult>(Task task, Action continuation, bool runSynchronously = false)
{
SynchronizationContext syncContext = SynchronizationContext.Current;
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
task.ContinueWith(innerTask =>
{
try
{
if (syncContext != null)
{
syncContext.Post(state =>
{
try
{
continuation();
tcs.TrySetFromTask(innerTask);
}
catch (Exception ex)
{
MarkExceptionsObserved(innerTask);
tcs.SetException(ex);
}
}, state: null);
}
else
{
continuation();
tcs.TrySetFromTask(innerTask);
}
}
catch (Exception ex)
{
MarkExceptionsObserved(innerTask);
tcs.TrySetException(ex);
}
}, runSynchronously ? TaskContinuationOptions.ExecuteSynchronously : TaskContinuationOptions.None);
return tcs.Task;
}
[SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes", Justification = "This general exception is not intended to be seen by the user")]
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This general exception is not intended to be seen by the user")]
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
internal static Action<Task> GetRethrowWithNoStackLossDelegate()
{
#if NETFX_CORE
return task => task.GetAwaiter().GetResult();
#else
MethodInfo getAwaiterMethod = typeof(Task).GetMethod("GetAwaiter", Type.EmptyTypes);
if (getAwaiterMethod != null)
{
// .NET 4.5 - dump the same code the 'await' keyword would have dumped
// >> task.GetAwaiter().GetResult()
// No-ops if the task completed successfully, else throws the originating exception complete with the correct call stack.
var taskParameter = Expression.Parameter(typeof(Task));
var getAwaiterCall = Expression.Call(taskParameter, getAwaiterMethod);
var getResultCall = Expression.Call(getAwaiterCall, "GetResult", Type.EmptyTypes);
var lambda = Expression.Lambda<Action<Task>>(getResultCall, taskParameter);
return lambda.Compile();
}
else
{
Func<Exception, Exception> prepForRemoting = null;
#if !MOBILE
try
{
if (AppDomain.CurrentDomain.IsFullyTrusted)
{
// .NET 4 - do the same thing Lazy<T> does by calling Exception.PrepForRemoting
// This is an internal method in mscorlib.dll, so pass a test Exception to it to make sure we can call it.
var exceptionParameter = Expression.Parameter(typeof(Exception));
var prepForRemotingCall = Expression.Call(exceptionParameter, "PrepForRemoting", Type.EmptyTypes);
var lambda = Expression.Lambda<Func<Exception, Exception>>(prepForRemotingCall, exceptionParameter);
var func = lambda.Compile();
func(new Exception()); // make sure the method call succeeds before assigning the 'prepForRemoting' local variable
prepForRemoting = func;
}
}
catch
{
} // If delegate creation fails (medium trust) we will simply throw the base exception.
#endif
return task =>
{
try
{
task.Wait();
}
catch (AggregateException ex)
{
Exception baseException = ex.GetBaseException();
if (prepForRemoting != null)
{
baseException = prepForRemoting(baseException);
}
throw baseException;
}
};
}
#endif
}
/// <summary>
/// Marks a Task as "exception observed". The Task is required to have been completed first.
/// </summary>
/// <remarks>
/// Useful for 'finally' clauses, as if the 'finally' action throws we'll propagate the new
/// exception and lose track of the inner exception.
/// </remarks>
[SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "unused", Justification = "We only call the property getter for its side effect; we don't care about the value.")]
public static void MarkExceptionsObserved(this Task task)
{
Contract.Assert(task.IsCompleted);
Exception unused = task.Exception;
}
/// <summary>
/// Calls the given continuation, after the given task has completed, if the task successfully ran
/// to completion (i.e., was not cancelled and did not fault).
/// </summary>
public static Task Then(this Task task, Action continuation, CancellationToken cancellationToken = default(CancellationToken), bool runSynchronously = false)
{
return task.ThenImpl(t => ToAsyncVoidTask(continuation), cancellationToken, runSynchronously);
}
/// <summary>
/// Calls the given continuation, after the given task has completed, if the task successfully ran
/// to completion (i.e., was not cancelled and did not fault).
/// </summary>
public static Task<TOuterResult> Then<TOuterResult>(this Task task, Func<TOuterResult> continuation, CancellationToken cancellationToken = default(CancellationToken), bool runSynchronously = false)
{
return task.ThenImpl(t => TaskHelpers.FromResult(continuation()), cancellationToken, runSynchronously);
}
/// <summary>
/// Calls the given continuation, after the given task has completed, if the task successfully ran
/// to completion (i.e., was not cancelled and did not fault).
/// </summary>
public static Task Then(this Task task, Func<Task> continuation, CancellationToken cancellationToken = default(CancellationToken), bool runSynchronously = false)
{
return task.Then(() => continuation().Then(() => default(AsyncVoid)),
cancellationToken, runSynchronously);
}
/// <summary>
/// Calls the given continuation, after the given task has completed, if the task successfully ran
/// to completion (i.e., was not cancelled and did not fault).
/// </summary>
public static Task<TOuterResult> Then<TOuterResult>(this Task task, Func<Task<TOuterResult>> continuation, CancellationToken cancellationToken = default(CancellationToken), bool runSynchronously = false)
{
return task.ThenImpl(t => continuation(), cancellationToken, runSynchronously);
}
/// <summary>
/// Calls the given continuation, after the given task has completed, if the task successfully ran
/// to completion (i.e., was not cancelled and did not fault). The continuation is provided with the
/// result of the task as its sole parameter.
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task Then<TInnerResult>(this Task<TInnerResult> task, Action<TInnerResult> continuation, CancellationToken cancellationToken = default(CancellationToken), bool runSynchronously = false)
{
return task.ThenImpl(t => ToAsyncVoidTask(() => continuation(t.Result)), cancellationToken, runSynchronously);
}
/// <summary>
/// Calls the given continuation, after the given task has completed, if the task successfully ran
/// to completion (i.e., was not cancelled and did not fault). The continuation is provided with the
/// result of the task as its sole parameter.
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task<TOuterResult> Then<TInnerResult, TOuterResult>(this Task<TInnerResult> task, Func<TInnerResult, TOuterResult> continuation, CancellationToken cancellationToken = default(CancellationToken), bool runSynchronously = false)
{
return task.ThenImpl(t => TaskHelpers.FromResult(continuation(t.Result)), cancellationToken, runSynchronously);
}
/// <summary>
/// Calls the given continuation, after the given task has completed, if the task successfully ran
/// to completion (i.e., was not cancelled and did not fault). The continuation is provided with the
/// result of the task as its sole parameter.
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task Then<TInnerResult>(this Task<TInnerResult> task, Func<TInnerResult, Task> continuation, CancellationToken token = default(CancellationToken), bool runSynchronously = false)
{
return task.ThenImpl(t => continuation(t.Result).ToTask<AsyncVoid>(), token, runSynchronously);
}
/// <summary>
/// Calls the given continuation, after the given task has completed, if the task successfully ran
/// to completion (i.e., was not cancelled and did not fault). The continuation is provided with the
/// result of the task as its sole parameter.
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task<TOuterResult> Then<TInnerResult, TOuterResult>(this Task<TInnerResult> task, Func<TInnerResult, Task<TOuterResult>> continuation, CancellationToken cancellationToken = default(CancellationToken), bool runSynchronously = false)
{
return task.ThenImpl(t => continuation(t.Result), cancellationToken, runSynchronously);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
private static Task<TOuterResult> ThenImpl<TTask, TOuterResult>(this TTask task, Func<TTask, Task<TOuterResult>> continuation, CancellationToken cancellationToken, bool runSynchronously)
where TTask : Task
{
// Stay on the same thread if we can
if (task.IsCompleted)
{
if (task.IsFaulted)
{
return TaskHelpers.FromErrors<TOuterResult>(task.Exception.InnerExceptions);
}
if (task.IsCanceled || cancellationToken.IsCancellationRequested)
{
return TaskHelpers.Canceled<TOuterResult>();
}
if (task.Status == TaskStatus.RanToCompletion)
{
try
{
return continuation(task);
}
catch (Exception ex)
{
return TaskHelpers.FromError<TOuterResult>(ex);
}
}
}
// Split into a continuation method so that we don't create a closure unnecessarily
return ThenImplContinuation(task, continuation, cancellationToken, runSynchronously);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
private static Task<TOuterResult> ThenImplContinuation<TOuterResult, TTask>(TTask task, Func<TTask, Task<TOuterResult>> continuation, CancellationToken cancellationToken, bool runSynchronously = false)
where TTask : Task
{
SynchronizationContext syncContext = SynchronizationContext.Current;
TaskCompletionSource<Task<TOuterResult>> tcs = new TaskCompletionSource<Task<TOuterResult>>();
task.ContinueWith(innerTask =>
{
if (innerTask.IsFaulted)
{
tcs.TrySetException(innerTask.Exception.InnerExceptions);
}
else if (innerTask.IsCanceled || cancellationToken.IsCancellationRequested)
{
tcs.TrySetCanceled();
}
else
{
if (syncContext != null)
{
syncContext.Post(state =>
{
try
{
tcs.TrySetResult(continuation(task));
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}, state: null);
}
else
{
tcs.TrySetResult(continuation(task));
}
}
}, runSynchronously ? TaskContinuationOptions.ExecuteSynchronously : TaskContinuationOptions.None);
return tcs.Task.FastUnwrap();
}
/// <summary>
/// Throws the first faulting exception for a task which is faulted. It attempts to preserve the original
/// stack trace when throwing the exception (which should always work in 4.5, and should also work in 4.0
/// when running in full trust). Note: It is the caller's responsibility not to pass incomplete tasks to
/// this method, because it does degenerate into a call to the equivalent of .Wait() on the task when it
/// hasn't yet completed.
/// </summary>
public static void ThrowIfFaulted(this Task task)
{
_rethrowWithNoStackLossDelegate(task);
}
/// <summary>
/// Adapts any action into a Task (returning AsyncVoid, so that it's usable with Task{T} extension methods).
/// </summary>
private static Task<AsyncVoid> ToAsyncVoidTask(Action action)
{
return TaskHelpers.RunSynchronously<AsyncVoid>(() =>
{
action();
return _defaultCompleted;
});
}
/// <summary>
/// Changes the return value of a task to the given result, if the task ends in the RanToCompletion state.
/// This potentially imposes an extra ContinueWith to convert a non-completed task, so use this with caution.
/// </summary>
public static Task<TResult> ToTask<TResult>(this Task task, CancellationToken cancellationToken = default(CancellationToken), TResult result = default(TResult))
{
if (task == null)
{
return null;
}
// Stay on the same thread if we can
if (task.IsCompleted)
{
if (task.IsFaulted)
{
return TaskHelpers.FromErrors<TResult>(task.Exception.InnerExceptions);
}
if (task.IsCanceled || cancellationToken.IsCancellationRequested)
{
return TaskHelpers.Canceled<TResult>();
}
if (task.Status == TaskStatus.RanToCompletion)
{
return TaskHelpers.FromResult(result);
}
}
// Split into a continuation method so that we don't create a closure unnecessarily
return ToTaskContinuation(task, result);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")]
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static Task<TResult> ToTaskContinuation<TResult>(Task task, TResult result)
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
task.ContinueWith(innerTask =>
{
if (task.Status == TaskStatus.RanToCompletion)
{
tcs.TrySetResult(result);
}
else
{
tcs.TrySetFromTask(innerTask);
}
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
/// <summary>
/// Attempts to get the result value for the given task. If the task ran to completion, then
/// it will return true and set the result value; otherwise, it will return false.
/// </summary>
[SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")]
public static bool TryGetResult<TResult>(this Task<TResult> task, out TResult result)
{
if (task.Status == TaskStatus.RanToCompletion)
{
result = task.Result;
return true;
}
result = default(TResult);
return false;
}
/// <summary>
/// Used as the T in a "conversion" of a Task into a Task{T}
/// </summary>
private struct AsyncVoid
{
}
}
[SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Packaged as one file to make it easy to link against")]
public abstract class CatchInfoBase<TTask>
where TTask : Task
{
private Exception _exception;
private TTask _task;
protected CatchInfoBase(TTask task)
{
Contract.Assert(task != null);
_task = task;
_exception = _task.Exception.GetBaseException(); // Observe the exception early, to prevent tasks tearing down the app domain
}
/// <summary>
/// The exception that was thrown to cause the Catch block to execute.
/// </summary>
public Exception Exception
{
get { return _exception; }
}
/// <summary>
/// Returns a CatchResult that re-throws the original exception.
/// </summary>
public CatchResult Throw()
{
return new CatchResult { Task = _task };
}
/// <summary>
/// Represents a result to be returned from a Catch handler.
/// </summary>
public struct CatchResult
{
/// <summary>
/// Gets or sets the task to be returned to the caller.
/// </summary>
internal TTask Task { get; set; }
}
}
[SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Packaged as one file to make it easy to link against")]
public class CatchInfo : CatchInfoBase<Task>
{
private static CatchResult _completed = new CatchResult { Task = TaskHelpers.Completed() };
public CatchInfo(Task task)
: base(task)
{
}
/// <summary>
/// Returns a CatchResult that returns a completed (non-faulted) task.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This would result in poor usability.")]
public CatchResult Handled()
{
return _completed;
}
/// <summary>
/// Returns a CatchResult that executes the given task and returns it, in whatever state it finishes.
/// </summary>
/// <param name="task">The task to return.</param>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This would result in poor usability.")]
public CatchResult Task(Task task)
{
return new CatchResult { Task = task };
}
/// <summary>
/// Returns a CatchResult that throws the given exception.
/// </summary>
/// <param name="ex">The exception to throw.</param>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This would result in poor usability.")]
public CatchResult Throw(Exception ex)
{
return new CatchResult { Task = TaskHelpers.FromError<object>(ex) };
}
}
[SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Packaged as one file to make it easy to link against")]
public class CatchInfo<T> : CatchInfoBase<Task<T>>
{
public CatchInfo(Task<T> task)
: base(task)
{
}
/// <summary>
/// Returns a CatchResult that returns a completed (non-faulted) task.
/// </summary>
/// <param name="returnValue">The return value of the task.</param>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This would result in poor usability.")]
public CatchResult Handled(T returnValue)
{
return new CatchResult { Task = TaskHelpers.FromResult(returnValue) };
}
/// <summary>
/// Returns a CatchResult that executes the given task and returns it, in whatever state it finishes.
/// </summary>
/// <param name="task">The task to return.</param>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This would result in poor usability.")]
public CatchResult Task(Task<T> task)
{
return new CatchResult { Task = task };
}
/// <summary>
/// Returns a CatchResult that throws the given exception.
/// </summary>
/// <param name="ex">The exception to throw.</param>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This would result in poor usability.")]
public CatchResult Throw(Exception ex)
{
return new CatchResult { Task = TaskHelpers.FromError<T>(ex) };
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.