In TPL, if an exception is thrown by a Task, that exception is captured and stored in Task.Exception, and then follows all the rules on observed exceptions. If it\'s never obser
I found a solution that works adequately some of the time.
var synchronizationContext = SynchronizationContext.Current;
var task = Task.Factory.StartNew(...);
task.ContinueWith(task =>
synchronizationContext.Post(state => {
if (!task.IsCanceled)
task.Wait();
}, null));
This schedules a call to task.Wait() on the UI thread. Since I don't do the Wait until I know the task is already done, it won't actually block; it will just check to see if there was an exception, and if so, it will throw. Since the SynchronizationContext.Post callback is executed straight from the message loop (outside the context of a Task), the TPL won't stop the exception, and it can propagate normally -- just as if it was an unhandled exception in a button-click handler.
One extra wrinkle is that I don't want to call WaitAll if the task was canceled. If you wait on a canceled task, TPL throws a TaskCanceledException, which it makes no sense to re-throw.
In my actual code, I have multiple tasks -- an initial task and multiple continuations. If any of those (potentially more than one) get an exception, I want to propagate an AggregateException back to the UI thread. Here's how to handle that:
var synchronizationContext = SynchronizationContext.Current;
var firstTask = Task.Factory.StartNew(...);
var secondTask = firstTask.ContinueWith(...);
var thirdTask = secondTask.ContinueWith(...);
Task.Factory.ContinueWhenAll(
new[] { firstTask, secondTask, thirdTask },
tasks => synchronizationContext.Post(state =>
Task.WaitAll(tasks.Where(task => !task.IsCanceled).ToArray()), null));
Same story: once all the tasks have completed, call WaitAll outside the context of a Task. It won't block, since the tasks are already completed; it's just an easy way to throw an AggregateException if any of the tasks faulted.
At first I worried that, if one of the continuation tasks used something like TaskContinuationOptions.OnlyOnRanToCompletion, and the first task faulted, then the WaitAll call might hang (since the continuation task would never run, and I worried that WaitAll would block waiting for it to run). But it turns out the TPL designers were cleverer than that -- if the continuation task won't be run because of OnlyOn or NotOn flags, that continuation task transitions to the Canceled state, so it won't block the WaitAll.
When I use the multiple-tasks version, the WaitAll call throws an AggregateException, but that AggregateException doesn't make it through to the ThreadException handler: instead only one of its inner exceptions gets passed to ThreadException. So if multiple tasks threw exceptions, only one of them reaches the thread-exception handler. I'm not clear on why this is, but I'm trying to figure it out.