To expand upon our discussion in the comments:
What you have here is a user-unhandled exception (not to be confused with unobserved exception) causing the debugger to break. If you were to run the program built in debug without actually attaching a debugger, it should behave exactly as you expect. The continuation will run, and it will observe the exception from the antecedent task.
From your perspective, you are handling the exception, and if you were to write some vanilla synchronous code such as this:
try
{
throw new Exception("Oops");
}
catch
{
}
... then the debugger is smart enough to work out that the exception is, indeed, handled, and treats it as such.
However, when you're dealing with task continuations, there are no similarly strong guarantees that your exception handling code will run. It can run on the same thread or a different thread, synchronously or asynchronously, or even not at all if the continuation fails to run (which can happen for a number of reasons that the runtime doesn't necessarily have control over). So the safe choice is for the debugger to say "I can't see this exception being handled anywhere in the immediate call stack, therefore it's user-unhandled".
To drive this point home, think about unobserved task exceptions. In .NET 4.0 they could tear down your program a solid minute after actually being thrown. That is how long it took for the runtime to work out with reasonable confidence that no task continuations actually looked at the exception. When you're debugging, you can't wait that long. If something looks unhandled, the safe choice is for the debugger to break immediately.
Finally, I should note that you can modify this behaviour by telling Visual Studio's debugger not to break on particular exception types (OperationCanceledException
would be a good candidate as it comes up in asynchronous code a lot) via Debug -> Windows -> Exception Settings.