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.