Catching Exceptions in Tasks

My base ViewModel, implemented a couple of years ago, provides this extension method for completing tasks, while preserving the user interface *:

protected void Work(Action job) { IsBusy = true; var stackTrace = new StackTrace(); var task = Task.Factory.StartNew(job) .ContinueWith(failedTask => HandleException(failedTask, stackTrace), TaskContinuationOptions.OnlyOnFaulted) .ContinueWith(_ => { IsBusy = false; }); } void HandleException(Task task, StackTrace stackTrace) { Dispatcher.BeginInvoke( () => { throw new Exception(task.Exception.InnerException.ToString() + stackTrace); }); } 

IsBusy is a property observed from the user interface to display a progress bar.

The idea of HandleExceptions is that they are observed and then thrown into the user interface stream, captured by the try/catch in the Main() method and registered before displaying a friendly message to the user and safely closing the application. The strace stack is passed so that the log includes information about the caller.

However, I recently started getting application crash reports without logging and friendly messages with this Windows dialog:

crash

Looking at the Windows event log, we got this EventData:

Application: xxxxApplication.Loader.exe

Framework Version: v4.0.30319

Description: The process was aborted due to an unhandled exception.

Exception Information: System.AggregateException

Stack: in System.Threading.Tasks.TaskExceptionHolder.Finalize ()

Am I doing something wrong with ContinueWith() ?

Is it possible that exceptions for tasks remain unobservable?


<sub> *: I know about BackgroundWorker . Probably at that time it seemed like a better idea or had additional benefits. Sub>

+4
source share
3 answers

I would suggest that you handle AppDomain.CurrentDomain.UnhandledException either to display your "friendly message" or (if this is not a solution for you), you could at least log errors in this event, and then see what stacktrace will determine where this task exception is not handled.

+1
source

I assume that here you have the conditions of the race. From Task.Exception link:

Tasks that throw unhandled exceptions save the received exception and throw it wrapped in an AggregateException into Wait calls or when accessing the Exception property. Any exceptions that are not observed by the time the task instance is garbage collected will apply to the finalizer thread. For more information and an example, see Exception Handling (Parallel Task Library).

Below, if your task is collected before the delegate passed to Dispatcher.BeginInvoke is called, an exception will be thrown in the main thread. Quick fix:

 void HandleException(Task task, StackTrace stackTrace) { // "observe" the exception var ex = task.Exception; Dispatcher.BeginInvoke( () => { throw new Exception(ex.InnerException.ToString() + stackTrace); }); } 
+1
source

I'm not sure, but you will get the original exception through the failedTask variable. It provides a non-zero AggregateException if the task fails.

This exception, in turn, is a Handle method , which you can provide to the delegate, where you can handle every exception contained in a collapsible exception and indicate, consider the exception handled.

In other words, if you want to throw the actual exception into the Dispatcher thread, I think you should make sure you mark the original exception that was handled before leaving the task chain.

0
source

Source: https://habr.com/ru/post/1441292/


All Articles