.nettask-parallel-librarydesign-decisions

Why does ContinueWith pass the Task as the parameter


I have a Task<T> t1. I want to run another Task t2 after t1 completes. I choose to use the .ContinueWith method of t1.

void ThenFrob(Task<Frobber> t1) {
    t1.ContinueWith(frobber => frobber.Frob())
}

Except, I cannot do this, because the Action parameter of Task<T> is passed the Task<T>, rather than T itself. Instead, I have to take the result of the parameter passed into my action to interact with it.

void ThenFrob(Task<Frobber> t1) {
    t1.ContinueWith(frobberTask => {
        var frobber = frobberTask.Result;
        frobber.frob();
    });
}

If the point of ContinueWith is to add another action to the chain, why wouldn't the language designers have simply passed the result of the previous task? Or, in the case of a non-generic Task expected a parameterless action?


Solution

  • ContinueWith runs the delegate when the task enters the task.IsCompleted == true state. Not when it enters the task.IsCompleted == true && task.IsFaulted == false && task.IsCanceled == false state, a task that threw a exception or a task that was canceled both get "completed" but will not produce a result.

    There are overloads you can pass in TaskContinuationOptions like TaskContinuationOptions.OnlyOnRanToCompletion to get behavior like you are describing, but it would be more complex to have a Action<T> overload for just that single enum option so they just use the general overload of Action<Task<T>> so you can use the same method if you are doing OnlyOnRanToCompletion or if you are doing OnlyOnFaulted.

    Also there is still useful information in a Task object of a completed task, if you are using the AsyncState property to pass metadata along if the completed task was not passed on to ContinueWith you would not have a way to get that data unless you used variable capture of a lambada expression.