41

I have some simple code as a repro:

var taskTest = Task.Factory.StartNew(() =>
{
    System.Threading.Thread.Sleep(5000);

}).ContinueWith((Task t) =>
{
    Console.WriteLine("ERR");
}, TaskContinuationOptions.OnlyOnFaulted);

try
{
    Task.WaitAll(taskTest);
}
catch (AggregateException ex)
{
    foreach (var e in ex.InnerExceptions)
        Console.WriteLine(e.Message + Environment.NewLine + e.StackTrace);
}

However, I'm getting an unexpected TaskCanceledException being thrown in the try catch block (it's in the AggregateException InnerExceptions object). "A task was canceled".

Why am I getting this exception? The Continuation for the task never fires, there was no exception generated by it, yet I still get the aggregate exception when waiting....

I'm hoping someone can explain how this makes sense to me :)

Redth
  • 5,464
  • 6
  • 34
  • 54
  • I had exactly the same scenario as you. I think it would have been more elegant and intuitive to just propagate the result of the previous task instead. Throwing a TaskCanceledException was quite surprising... – NickL May 13 '14 at 13:53

1 Answers1

56

You're not waiting on a task with an OnlyOnFaulted continuation - you're waiting on that continuation (returned by ContinueWith). The continuation is never going to fire because the original task returned normally, so it's acting as if it were cancelled.

Makes sense to me.

I suspect you want to create the task, add the continuation, but then wait on the original task:

var taskTest = Task.Factory.StartNew(() =>
{
    System.Threading.Thread.Sleep(5000);

});
taskTest.ContinueWith((Task t) =>
{
    Console.WriteLine("ERR");
}, TaskContinuationOptions.OnlyOnFaulted);
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Great explanation, just one comment, in this case it is probably better not to use the Factory.StartNew() method, because the task can start before the continuation is defined. Here I would use new Task() ctor, then set the ContinueWith() and finally call the Start() method. Does it make sense? – Jorge Fioranelli Jul 01 '12 at 23:12
  • 1
    @JorgeFioranelli: Why would it be a problem for the task to start before the continuation is defined? The TPL will handle this with no problems, I believe. – Jon Skeet Jul 02 '12 at 05:41
  • 2
    Not sure, but I believe it is possible that the task throws an exception before the continuation is added, and then the handler won't be called. Am I right? – Jorge Fioranelli Jul 03 '12 at 07:14
  • 7
    @JorgeFioranelli - If the continuation is attached to a completed task, it will execute immediately if the task completed with an exception. (It will cancel if the task did not fault.) – Olly Apr 12 '13 at 20:46
  • If we await the original task, and we finish sooner than its exception handling continuation does, then the application _could_ exit before the exception handler finishes, correct? How might we best avoid that? Await the _continuation_, but without regard for how it completes? `await continuationTask.ContinueWith(task => {});` – Timo Dec 13 '18 at 12:43
  • I see that my previous suggestion would not have the intended effect, as `ContinueWith()` does not expect you to pass it a `Task`-returning method, and thus will not await an async error handler. How else might we solve this? – Timo Dec 13 '18 at 13:25
  • @Timo: I would suggest asking in a new question, to be honest. (Bear in mind that when this question was asked and answered, async/await wasn't even a thing.) – Jon Skeet Dec 13 '18 at 15:02