So I was told recently that how I was using my .ContinueWith for Tasks was not the proper way to use them. I have yet to find evidence of this on the internet so I will ask you guys and see what the answer is. Here is an example of how I use .ContinueWith:
public Task DoSomething()
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine("Step 1");
})
.ContinueWith((prevTask) =>
{
Console.WriteLine("Step 2");
})
.ContinueWith((prevTask) =>
{
Console.WriteLine("Step 3");
});
}
Now I know this is a simple example and it will run very fast, but just assume each task does some longer operation. So, what I was told is that in the .ContinueWith, you need to say prevTask.Wait(); otherwise you could do work before the previous task finishes. Is that even possible? I assumed my second & third task would only run once their previous task finishes.
What I was told how to write the code:
public Task DoSomething()
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine("Step 1");
})
.ContinueWith((prevTask) =>
{
prevTask.Wait();
Console.WriteLine("Step 2");
})
.ContinueWith((prevTask) =>
{
prevTask.Wait();
Console.WriteLine("Step 3");
});
}
I will reiterate what many have spoken already,
prevTask.Wait()
is unnecessary.For more examples one can go to Chaining Tasks using Continuation Tasks, yet another link by Microsoft with good examples.
By Accessing
Task.Result
you are actually doing similar logic totask.wait
Who told you that?
Quoting MSDN:
Also, what would be the purpose of Continue With if it wasn't waiting for the previous task to be completed?
You can even test it by yourself:
Ehhh.... I think some of the current answers are missing something: what happens with exceptions?
The only reason you would call
Wait
in a continuation would be to observe a potential exception from the antecedent in the continuation itself. The same observation would happen if you accessedResult
in the case of aTask<T>
and also if you manually accessed theException
property. Frankly, I wouldn't callWait
or accessResult
because if there is an exception you'll pay the price of re-raising it which is unnecessary overhead. Instead you can just check theIsFaulted
property off the antecedentTask
. Alternatively you can create forked workflows by chaining on multiple sibling continuations that only fire based on either success or failure withTaskContinuationOptions.OnlyOnRanToCompletion
andTaskContinuationOptions.OnlyOnFaulted
.Now, it's not necessary to observe the exception of the antecedent in the continuation, but you may not want your workflow to move forward if, say, "Step 1" failed. In that case: specifying
TaskContinuationOptions.NotOnFaulted
to yourContinueWith
calls would prevent the continuation logic from ever even firing.Keep in mind that, if your own continuations don't observe the exception, the person who is waiting on this overall workflow to complete is going to be the one to observe it. Either they're
Wait
ing on theTask
upstream or have tacked on their own continuation to know when it is complete. If it is the latter, their continuation would need to use the aforementioned observation logic.You might also want to consider using Task.Run instead of Task.Factory.StartNew.
Stephen Cleary's blog post and the Stephen Toub's post that he references explain the differences. There is also a discussion in this answer.
From the MSDN on
Task.Continuewith
I think that the way you expect it to work in the first example is the correct way.