对于这个问题,我正在检查任务t1
和延续函数f2
的行为,其中f2
在t1
完成后执行。目标框架是带有Task Parallel Library for .NET 3.5的.NET 4.0或.NET 3.5,因此不允许使用async
和await
。执行此操作的最简单方法如下:
// if f2 doesn't return a Task
Task result = t1.ContinueWith(f2);
// if f2 returns a Task
Task result = t1.ContinueWith(f2).Unwrap();
虽然上面的代码很好地处理了成功案例,但对于取消和故障的情况来说,它是次优的。为了解决这个问题,我对能够满足以下所有要求的最干净的方法感兴趣:
如果取消t1
,则f2
未执行且result
的状态为TaskStatus.Canceled
。
如果t1
出现故障并且f2
无法处理出现故障的先前任务,则f2
不会执行,而result.Exception
返回的异常与t1.Exception
返回的值(即异常未包含在另一个AggregateException
中)。
如果t1
成功完成,或t1
出现故障并且f2
处理出现故障的先前任务,那么结果的行为与上面列出的ContinueWith
代码。
为了解决这种情况,我创建了两个扩展方法Chain
(处理f2
未返回Task
的情况)和ChainAsync
(处理f2
返回Task
)的情况。
/// <summary>
/// Execute a continuation when a task completes. The <paramref name="supportsErrors"/>
/// parameter specifies whether the continuation is executed if the antecedent task is faulted.
/// </summary>
/// <remarks>
/// <para>If the antecedent task is cancelled, or faulted with <paramref name="supportsErrors"/>
/// set to <see langword="false"/>, the status of the antecedent is directly applied to the task
/// returned by this method; it is not wrapped in an additional <see cref="AggregateException"/>.
/// </para>
/// </remarks>
/// <typeparam name="TSource">The type of the result produced by the antecedent <see cref="Task{TResult}"/>.</typeparam>
/// <typeparam name="TResult">The type of the result produced by the continuation <see cref="Task{TResult}"/>.</typeparam>
/// <param name="task">The antecedent task.</param>
/// <param name="continuationFunction">The continuation function to execute when <paramref name="task"/> completes successfully.</param>
/// <param name="supportsErrors"><see langword="true"/> if the <paramref name="continuationFunction"/> properly handles a faulted antecedent task; otherwise, <see langword="false"/>.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation. When the task completes successfully,
/// the <see cref="Task{TResult}.Result"/> property will contain the result returned from the <paramref name="continuationFunction"/>.</returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="task"/> is <see langword="null"/>.
/// <para>-or-</para>
/// <para>If <paramref name="continuationFunction"/> is <see langword="null"/>.</para>
/// </exception>
public static Task<TResult> Chain<TSource, TResult>(this Task<TSource> task, Func<Task<TSource>, TResult> continuationFunction, bool supportsErrors)
{
if (task == null)
throw new ArgumentNullException("task");
if (continuationFunction == null)
throw new ArgumentNullException("continuationFunction");
TaskCompletionSource<TResult> completionSource = new TaskCompletionSource<TResult>();
TaskContinuationOptions successContinuationOptions = supportsErrors ? TaskContinuationOptions.NotOnCanceled : TaskContinuationOptions.OnlyOnRanToCompletion;
task
.ContinueWith(continuationFunction, successContinuationOptions)
.ContinueWith(
t =>
{
if (task.Status == TaskStatus.RanToCompletion || supportsErrors && task.Status == TaskStatus.Faulted)
completionSource.SetFromTask(t);
}, TaskContinuationOptions.ExecuteSynchronously);
TaskContinuationOptions failedContinuationOptions = supportsErrors ? TaskContinuationOptions.OnlyOnCanceled : TaskContinuationOptions.NotOnRanToCompletion;
task
.ContinueWith(t => completionSource.SetFromTask(t), TaskContinuationOptions.ExecuteSynchronously | failedContinuationOptions);
return completionSource.Task;
}
/// <summary>
/// Execute a continuation task when a task completes. The continuation
/// task is created by a continuation function, and then unwrapped to form the result
/// of this method. The <paramref name="supportsErrors"/> parameter specifies whether
/// the continuation is executed if the antecedent task is faulted.
/// </summary>
/// <remarks>
/// <para>If the antecedent <paramref name="task"/> is cancelled, or faulted with
/// <paramref name="supportsErrors"/> set to <see langword="false"/>, the status
/// of the antecedent is directly applied to the task returned by this method; it is
/// not wrapped in an additional <see cref="AggregateException"/>.
/// </para>
/// </remarks>
/// <typeparam name="TSource">The type of the result produced by the antecedent <see cref="Task{TResult}"/>.</typeparam>
/// <typeparam name="TResult">The type of the result produced by the continuation <see cref="Task{TResult}"/>.</typeparam>
/// <param name="task">The antecedent task.</param>
/// <param name="continuationFunction">The continuation function to execute when <paramref name="task"/> completes successfully. The continuation function returns a <see cref="Task{TResult}"/> which provides the final result of the continuation.</param>
/// <param name="supportsErrors"><see langword="true"/> if the <paramref name="continuationFunction"/> properly handles a faulted antecedent task; otherwise, <see langword="false"/>.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation. When the task completes successfully,
/// the <see cref="Task{TResult}.Result"/> property will contain the result provided by the
/// <see cref="Task{TResult}.Result"/> property of the task returned from <paramref name="continuationFunction"/>.</returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="task"/> is <see langword="null"/>.
/// <para>-or-</para>
/// <para>If <paramref name="continuationFunction"/> is <see langword="null"/>.</para>
/// </exception>
public static Task<TResult> ChainAsync<TSource, TResult>(this Task<TSource> task, Func<Task<TSource>, Task<TResult>> continuationFunction, bool supportsErrors)
{
if (task == null)
throw new ArgumentNullException("task");
if (continuationFunction == null)
throw new ArgumentNullException("continuationFunction");
TaskCompletionSource<TResult> completionSource = new TaskCompletionSource<TResult>();
TaskContinuationOptions successContinuationOptions = supportsErrors ? TaskContinuationOptions.NotOnCanceled : TaskContinuationOptions.OnlyOnRanToCompletion;
task
.ContinueWith(continuationFunction, successContinuationOptions)
.Unwrap()
.ContinueWith(
t =>
{
if (task.Status == TaskStatus.RanToCompletion || supportsErrors && task.Status == TaskStatus.Faulted)
completionSource.SetFromTask(t);
}, TaskContinuationOptions.ExecuteSynchronously);
TaskContinuationOptions failedContinuationOptions = supportsErrors ? TaskContinuationOptions.OnlyOnCanceled : TaskContinuationOptions.NotOnRanToCompletion;
task
.ContinueWith(t => completionSource.SetFromTask(t), TaskContinuationOptions.ExecuteSynchronously | failedContinuationOptions);
return completionSource.Task;
}
正如您所看到的,这些实现会在对ContinueWith
的另外两次调用以及每个延续的TaskCompletionSource<TResult>
分配中产生结果,这让我有两个问题。
根据上述目标列表,是否存在这些扩展方法返回的任务不按预期方式运行的情况?
在成功和错误的情况下,是否有更有效的方法(更少的资源)来实现类似的结果?