anelson
anelson

Reputation: 2599

How to propagate a Task's Canceled status to a continuation task

I am using the Task Parallel Library in my application. I have a Task (let's call it "DoSomething") which may be canceled. Whether the task is faulted, canceled, or completes successfully, I have a continuation attached to that task which performs some cleanup.

In the code that launches this task, I want to return a Task object whose status (faulted, canceled, ran to completion) reflects the status of the DoSomething task, however it's important that this task I return not reflect this status until the continuation task executes.

Here's an example:

public Task Start(CancellationToken token)
{
    var doSomethingTask = Task.Factory.StartNew(DoSomething
                                                , token);

    var continuationTask = doSomethingTask.ContinueWith
                (
                 (antecedent) =>
                     {
                         if (antecedent.IsFaulted || antecedent.IsCanceled)
                         {
                             //Do failure-specific cleanup
                         }

   //Do general cleanup without regard to failure or success
                      }
                 );

//TODO: How do I return a Task obj which Status reflect the status of doSomethingTask,
//but will not transition to that status until continuationTask completes?
}

I could use a TaskCompletionSource, but that seems kludgy. Any other ideas?

Upvotes: 4

Views: 1033

Answers (1)

Arne Claassen
Arne Claassen

Reputation: 14404

I think that TaskCompletionSource is actually ideal for this scenario. I.e. you are trying to return a Task as a signal of completion of your work, but manually control when and how that task reports its status. You could easily hide the boiler plate required for this with an extension method like this:

public static Task<T> WithCleanup<T>(this Task<T> t, Action<Task<T>> cleanup) {
    var cleanupTask = t.ContinueWith(cleanup);
    var completion = new TaskCompletionSource<T>();
    cleanupTask.ContinueWith(_ => {
        if(t.IsCanceled) {
            completion.SetCanceled();
        } else if(t.IsFaulted) {
            completion.SetException(t.Exception);
        } else {
            completion.SetResult(t.Result);
        }
    });
    return completion.Task;
}

and call it like this:

var doSomethingTask = Task.Factory
  .StartNew<object>(DoSomething, token)
  .WithCleanup(Cleanup);

The only real caveat is that you can't do this with plain old Task since there is no non-generic TaskCompletionSource.

Upvotes: 7

Related Questions