title: Efficiently chaining tasks
body: <p>For this question, I'm examining the behavior of a task <code>t1</code> and a continuation function <code>f2</code>, where <code>f2</code> executes after <code>t1</code> completes. The target framework is .NET 4.0 or .NET 3.5 with the <a href="http://www.nuget.org/packages/TaskParallelLibrary/" rel="nofollow">Task Parallel Library for .NET 3.5</a>, so the use of <code>async</code> and <code>await</code> is not allowed. The simplest way of executing this is something like the following:</p>
  
  <pre><code>// if f2 doesn't return a Task
  Task result = t1.ContinueWith(f2);
  
  // if f2 returns a Task
  Task result = t1.ContinueWith(f2).Unwrap();
  </code></pre>
  
  <p>While the above code handles the success case well, it's suboptimal for cases of cancellation and faults. To address the situation, I'm interested in the cleanest approach which meets all of the following:</p>
  
  <ol>
  <li><p>If <code>t1</code> is cancelled, then <code>f2</code> is not executed and the status of <code>result</code> is <code>TaskStatus.Canceled</code>.</p></li>
  <li><p>If <code>t1</code> is faulted and <code>f2</code> does not handle faulted antecedent tasks, then <code>f2</code> is not executed and the exception returned by <code>result.Exception</code> is the same as the value returned by <code>t1.Exception</code> (i.e. the exception is not wrapped in another <code>AggregateException</code>).</p></li>
  <li><p>If <code>t1</code> completes successfully, or if <code>t1</code> is faulted and <code>f2</code> <em>does</em> handle faulted antecedent tasks, then the result behaves identically to the <code>ContinueWith</code> code listed above.</p></li>
  </ol>
  
  <p>To address this situation, I created two extension methods <code>Chain</code> (which handles the case where <code>f2</code> doesn't return a <code>Task</code>), and <code>ChainAsync</code> (which handles the case where <code>f2</code> returns a <code>Task</code>).</p>
  
  <pre><code>/// &lt;summary&gt;
  /// Execute a continuation when a task completes. The &lt;paramref name="supportsErrors"/&gt;
  /// parameter specifies whether the continuation is executed if the antecedent task is faulted.
  /// &lt;/summary&gt;
  /// &lt;remarks&gt;
  /// &lt;para&gt;If the antecedent task is cancelled, or faulted with &lt;paramref name="supportsErrors"/&gt;
  /// set to &lt;see langword="false"/&gt;, the status of the antecedent is directly applied to the task
  /// returned by this method; it is not wrapped in an additional &lt;see cref="AggregateException"/&gt;.
  /// &lt;/para&gt;
  /// &lt;/remarks&gt;
  /// &lt;typeparam name="TSource"&gt;The type of the result produced by the antecedent &lt;see cref="Task{TResult}"/&gt;.&lt;/typeparam&gt;
  /// &lt;typeparam name="TResult"&gt;The type of the result produced by the continuation &lt;see cref="Task{TResult}"/&gt;.&lt;/typeparam&gt;
  /// &lt;param name="task"&gt;The antecedent task.&lt;/param&gt;
  /// &lt;param name="continuationFunction"&gt;The continuation function to execute when &lt;paramref name="task"/&gt; completes successfully.&lt;/param&gt;
  /// &lt;param name="supportsErrors"&gt;&lt;see langword="true"/&gt; if the &lt;paramref name="continuationFunction"/&gt; properly handles a faulted antecedent task; otherwise, &lt;see langword="false"/&gt;.&lt;/param&gt;
  /// &lt;returns&gt;A &lt;see cref="Task"/&gt; representing the asynchronous operation. When the task completes successfully,
  /// the &lt;see cref="Task{TResult}.Result"/&gt; property will contain the result returned from the &lt;paramref name="continuationFunction"/&gt;.&lt;/returns&gt;
  /// &lt;exception cref="ArgumentNullException"&gt;
  /// If &lt;paramref name="task"/&gt; is &lt;see langword="null"/&gt;.
  /// &lt;para&gt;-or-&lt;/para&gt;
  /// &lt;para&gt;If &lt;paramref name="continuationFunction"/&gt; is &lt;see langword="null"/&gt;.&lt;/para&gt;
  /// &lt;/exception&gt;
  public static Task&lt;TResult&gt; Chain&lt;TSource, TResult&gt;(this Task&lt;TSource&gt; task, Func&lt;Task&lt;TSource&gt;, TResult&gt; continuationFunction, bool supportsErrors)
  {
      if (task == null)
          throw new ArgumentNullException("task");
      if (continuationFunction == null)
          throw new ArgumentNullException("continuationFunction");
  
      TaskCompletionSource&lt;TResult&gt; completionSource = new TaskCompletionSource&lt;TResult&gt;();
  
      TaskContinuationOptions successContinuationOptions = supportsErrors ? TaskContinuationOptions.NotOnCanceled : TaskContinuationOptions.OnlyOnRanToCompletion;
      task
          .ContinueWith(continuationFunction, successContinuationOptions)
          .ContinueWith(
              t =&gt;
              {
                  if (task.Status == TaskStatus.RanToCompletion || supportsErrors &amp;&amp; task.Status == TaskStatus.Faulted)
                      completionSource.SetFromTask(t);
              }, TaskContinuationOptions.ExecuteSynchronously);
  
      TaskContinuationOptions failedContinuationOptions = supportsErrors ? TaskContinuationOptions.OnlyOnCanceled : TaskContinuationOptions.NotOnRanToCompletion;
      task
          .ContinueWith(t =&gt; completionSource.SetFromTask(t), TaskContinuationOptions.ExecuteSynchronously | failedContinuationOptions);
  
      return completionSource.Task;
  }
  
  /// &lt;summary&gt;
  /// 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 &lt;paramref name="supportsErrors"/&gt; parameter specifies whether
  /// the continuation is executed if the antecedent task is faulted.
  /// &lt;/summary&gt;
  /// &lt;remarks&gt;
  /// &lt;para&gt;If the antecedent &lt;paramref name="task"/&gt; is cancelled, or faulted with
  /// &lt;paramref name="supportsErrors"/&gt; set to &lt;see langword="false"/&gt;, the status
  /// of the antecedent is directly applied to the task returned by this method; it is
  /// not wrapped in an additional &lt;see cref="AggregateException"/&gt;.
  /// &lt;/para&gt;
  /// &lt;/remarks&gt;
  /// &lt;typeparam name="TSource"&gt;The type of the result produced by the antecedent &lt;see cref="Task{TResult}"/&gt;.&lt;/typeparam&gt;
  /// &lt;typeparam name="TResult"&gt;The type of the result produced by the continuation &lt;see cref="Task{TResult}"/&gt;.&lt;/typeparam&gt;
  /// &lt;param name="task"&gt;The antecedent task.&lt;/param&gt;
  /// &lt;param name="continuationFunction"&gt;The continuation function to execute when &lt;paramref name="task"/&gt; completes successfully. The continuation function returns a &lt;see cref="Task{TResult}"/&gt; which provides the final result of the continuation.&lt;/param&gt;
  /// &lt;param name="supportsErrors"&gt;&lt;see langword="true"/&gt; if the &lt;paramref name="continuationFunction"/&gt; properly handles a faulted antecedent task; otherwise, &lt;see langword="false"/&gt;.&lt;/param&gt;
  /// &lt;returns&gt;A &lt;see cref="Task"/&gt; representing the asynchronous operation. When the task completes successfully,
  /// the &lt;see cref="Task{TResult}.Result"/&gt; property will contain the result provided by the
  /// &lt;see cref="Task{TResult}.Result"/&gt; property of the task returned from &lt;paramref name="continuationFunction"/&gt;.&lt;/returns&gt;
  /// &lt;exception cref="ArgumentNullException"&gt;
  /// If &lt;paramref name="task"/&gt; is &lt;see langword="null"/&gt;.
  /// &lt;para&gt;-or-&lt;/para&gt;
  /// &lt;para&gt;If &lt;paramref name="continuationFunction"/&gt; is &lt;see langword="null"/&gt;.&lt;/para&gt;
  /// &lt;/exception&gt;
  public static Task&lt;TResult&gt; ChainAsync&lt;TSource, TResult&gt;(this Task&lt;TSource&gt; task, Func&lt;Task&lt;TSource&gt;, Task&lt;TResult&gt;&gt; continuationFunction, bool supportsErrors)
  {
      if (task == null)
          throw new ArgumentNullException("task");
      if (continuationFunction == null)
          throw new ArgumentNullException("continuationFunction");
  
      TaskCompletionSource&lt;TResult&gt; completionSource = new TaskCompletionSource&lt;TResult&gt;();
  
      TaskContinuationOptions successContinuationOptions = supportsErrors ? TaskContinuationOptions.NotOnCanceled : TaskContinuationOptions.OnlyOnRanToCompletion;
      task
          .ContinueWith(continuationFunction, successContinuationOptions)
          .Unwrap()
          .ContinueWith(
              t =&gt;
              {
                  if (task.Status == TaskStatus.RanToCompletion || supportsErrors &amp;&amp; task.Status == TaskStatus.Faulted)
                      completionSource.SetFromTask(t);
              }, TaskContinuationOptions.ExecuteSynchronously);
  
      TaskContinuationOptions failedContinuationOptions = supportsErrors ? TaskContinuationOptions.OnlyOnCanceled : TaskContinuationOptions.NotOnRanToCompletion;
      task
          .ContinueWith(t =&gt; completionSource.SetFromTask(t), TaskContinuationOptions.ExecuteSynchronously | failedContinuationOptions);
  
      return completionSource.Task;
  }
  </code></pre>
  
  <p>As you can see, these implementations create result in two additional calls to <code>ContinueWith</code> plus an allocation of <code>TaskCompletionSource&lt;TResult&gt;</code> <em>per continuation</em>, which leaves me with two questions.</p>
  
  <ol>
  <li><p>Are there any cases where the tasks returned by these extension methods do not behave in the expected manner, according to the list of goals above?</p></li>
  <li><p>Is there a more efficient way (fewer resources in play) to achieve similar results in both success and error cases?</p></li>
  </ol>