http://msdn.microsoft.com/en-us/magazine/gg598924.aspx
Why exceptions are not propagated by WPF Dispatcher.Invoke?
How can I allow Task exceptions to propagate back to the UI thread?
In the code below I need to propagate execeptions that are thrown in the tasks and their continuations back up to the ui thread where they will be handled by LogException. If I need to re-throw an exception somewhere along the line thats fine with me. Whatever works. How do I do that?
I referenced some questions that are similar to mine but I do not see an answer that is relevant to my app.
Edit 3: posted a simplified example
Edit 2: See this: http://msdn.microsoft.com/en-us/library/dd997415(v=vs.100).aspx
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
FireAndForget();
WaitOnTask();
}
private void FireAndForget()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
throw new Exception("boo");
});
Task c1 = t1.ContinueWith((t) =>
{
// The app global exception handler will not catch this.
}, TaskContinuationOptions.OnlyOnFaulted);
//MessageBox.Show("Task is running");
}
private void WaitOnTask()
{
Task t1 = Task.Factory.StartNew(() =>
{
throw new Exception("boo");
});
try
{
t1.Wait();
}
catch (Exception ex)
{
// The app global exception handler will catch this:
throw new Exception("Task", ex);
}
}
}
public partial class App : Application
{
public App()
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Application.Current.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(Current_DispatcherUnhandledException);
//System.Threading.Tasks.TaskScheduler.UnobservedTaskException += new EventHandler<System.Threading.Tasks.UnobservedTaskExceptionEventArgs>(TaskScheduler_UnobservedTaskException);
}
void TaskScheduler_UnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs e)
{
LogException(e.Exception);
}
void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
LogException(e.Exception);
}
void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
LogException(e.ExceptionObject as Exception);
}
private void LogException(Exception ex)
{
// log it
string error = "This app has encountered an unexpected error . The error message is:" + Environment.NewLine + ex.Message + Environment.NewLine;
Exception tmp = ex.InnerException;
while (tmp != null)
{
error += "Inner exception is: " + Environment.NewLine + tmp.Message + Environment.NewLine;
tmp = tmp.InnerException;
}
error += "Please press OK to exit.";
MessageBox.Show(error, "Error");
Environment.Exit(-1);
}
}
When you use StartNew
or ContinueWith
, any exceptions are placed on the returned Task
.
There are two problems with marshaling exceptions:
Task.Exception
wraps your exception in an AggregateException
.For the first problem, some people use the Flatten
or Handle
members to work directly with AggregateException
. I prefer unwrapping the exceptions by dealing with Task.Exception.InnerException
instead of Task.Exception
.
For the second problem, some people work around it by wrapping it in another exception, but I have taken an alternative approach. .NET 4.5 introduced ExceptionDispatchInfo
, which is the correct way to do this. In .NET 4.0 you can hack something like this:
public static Exception Rethrow(this Exception ex)
{
typeof(Exception).GetMethod("PrepForRemoting",
BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(ex, new object[0]);
throw ex;
}
I'm not sure if i'm missing something here, but if you use TaskScheduler.FromCurrentSynchronizationContext() as the second parameter to ContinueWith then it will be marshaled back onto your UX thread.
I actually wrote a blog post about it if you want a little more of a sample. http://www.briankeating.net/post/Why-I-love-the-Task-library
Kr, Brian.
TaskScheduler.UnobservedTaskException
instead of Dispatcher.UnhandledException
- ghord
The answer to the question is found here: http://blogs.msdn.com/b/pfxteam/archive/2009/05/31/9674669.aspx
Basically there are two scenarios: Situations where you can wait on the task and situations where you cannot i.e. fire and forget. In situations where you can wait on the task, wrap it in a try block as shown in the question and rethrow the error. The global app handler will catch it.
In situtions where you cannot wait on the task you have to call your logger manually. There is no application level handler that will catch the error. There is a possibility that TaskScheduler.UnobservedTaskException will fire, however that event is IMHO highly circumstantial and fragile and not a good option.
To propagate the exceptions in your code you need to Wait
on all the tasks. If you make the following changes to your FireAndForget
method the Exception
in the nested Task
will be propagated back to the calling thread.
private void FireAndForget()
{
var tasks = new Task[2];
tasks[0] = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
throw new Exception("boo");
});
tasks[1] = tasks[0].ContinueWith((t) =>
{
throw new Exception("nested boo", tasks[0].Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
try
{
Task.WaitAll(tasks);
}
catch (AggregateException ex)
{
throw new Exception("Task", ex);
}
}
Of course this is no longer a "fire and forget" method. If waiting on the tasks is undesirable you will need to write to your log file from within the continuation.
You can await
the completion of the task to receive exception from the task code.
try{
await Task.Factory.StartNew(() => throw Exception("hello"));
}catch{
// will get exception here
}
async
/await
instead ofContinueWith
? - Stephen ClearyMicrosoft.Bcl.Async
? - Stephen Clearyasync
would really simplify your app, but I'll post an answer in a few minutes that should work. - Stephen Cleary