有效地链接任务

时间:2014-02-07 20:39:53

标签: c# .net error-handling task-parallel-library continuations

对于这个问题,我正在检查任务t1和延续函数f2的行为,其中f2t1完成后执行。目标框架是带有Task Parallel Library for .NET 3.5的.NET 4.0或.NET 3.5,因此不允许使用asyncawait。执行此操作的最简单方法如下:

// if f2 doesn't return a Task
Task result = t1.ContinueWith(f2);

// if f2 returns a Task
Task result = t1.ContinueWith(f2).Unwrap();

虽然上面的代码很好地处理了成功案例,但对于取消和故障的情况来说,它是次优的。为了解决这个问题,我对能够满足以下所有要求的最干净的方法感兴趣:

  1. 如果取消t1,则f2未执行且result的状态为TaskStatus.Canceled

  2. 如果t1出现故障并且f2无法处理出现故障的先前任务,则f2不会执行,而result.Exception返回的异常与t1.Exception返回的值(即异常未包含在另一个AggregateException中)。

  3. 如果t1成功完成,或t1出现故障并且f2 处理出现故障的先前任务,那么结果的行为与上面列出的ContinueWith代码。

  4. 为了解决这种情况,我创建了两个扩展方法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> 分配中产生结果,这让我有两个问题。

    1. 根据上述目标列表,是否存在这些扩展方法返回的任务不按预期方式运行的情况?

    2. 在成功和错误的情况下,是否有更有效的方法(更少的资源)来实现类似的结果?

0 个答案:

没有答案