I am currently struggling to show a dialog to the users of my WinUI3 application in case of an unhandled exception. In that case I want to present the user a dialog with some short information that some 'technical error occurred' (with optional additional information about the error), to avoid the situation that the application simply crashes.
In my specific scenario the exception comes from a background thread.
In the view model part of the application I call code that accesses the database periodically (every 10 seconds) in order to look up some data. If there is an exception in the data lookup, it bubbles up until it reaches my App class.
In the App class, I set up code to attach event handlers for various 'UnhandledException' events:
private void RegisterUnhandledExceptionHandler()
{
UnhandledException += (_, args) =>
{
Application_UnhandledException(this, args);
};
System.Threading.Tasks.TaskScheduler.UnobservedTaskException += (_, _) =>
{
Application_UnhandledException(this, null);
};
Current.UnhandledException += (_, args) =>
{
Application_UnhandledException(this, args);
};
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
// --> this handler gets called in my scenario (exception from background thread)
// 'IsTerminating' property in 'args' variable is true here
Application_UnhandledException(this, null);
};
}
As you can see, I am not very sure which events I should address...
In my case, the last of the four events is raised (AppDomain.CurrentDomain.UnhandledException), where I made the comment "this handler gets called in my scenario (exception from background thread)".
I addition I noticed that the IsTerminating property of the UnhandledExceptionEventHandler instance (the args variable) is true in this case. That is why I am not sure if it is possible to show a dialog at all.
Here is the code in the App class that handles the exception:
private async void Application_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
string exceptionMessage = "Techical error occured";
if (e != null)
{
e.Handled = true;
exceptionMessage = e.ToString();
}
// Show dialog with message
InformationDialog dialog = new InformationDialog(); // COMException --> The application called an interface that was marshalled for a different thread.
dialog.MessageTextBlock.Text = message;
await dialog.ShowAsync();
}
I shortened the "Show dialog with message" part a bit, but what I do here is showing a WinUI dialog that derives from ContentDialog (my InformationDialog class derives from ContentDialog).
Unfortunately when the code reaches the statement InformationDialog dialog = new InformationDialog() there is a COMException --> The application called an interface that was marshalled for a different thread.
I do know that it is not possible to access controls or show dialogs of the UI thread from a background thread.
So I tried to dispatch the code that shows the dialog to the UI thread using Task.Factory.StartNew and using TaskScheduler.FromCurrentSynchronizationContext():
private async void Application_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
string exceptionMessage = "Techical error occured";
if (e != null)
{
e.Handled = true;
exceptionMessage = e.ToString();
}
await Task.Factory.StartNew(
() =>
{
// await and show dialog here
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
But now I get another exception "System.InvalidOperationException: 'The current SynchronizationContext may not be used as a TaskScheduler."
What am I am doing wrong?
For the sake of completeness, this is that code in my view model layer, that initiates the database lookup.
It uses reactive extensions to execute a LookupDataFromDatabase method every 10 seconds.
When LookupDataFromDatabase causes an exception it creates the scenario I want to address.
public void Init()
{
// Use reactive extension to start timer: https://stackoverflow.com/a/4452625/4424024
var timer = Observable.Interval(TimeSpan.FromMilliseconds(10000));
timer.Subscribe(_ => LookupDataFromDatabase());
}
I am aware that I could also catch the exception more close to this code, but I do want to have a properly working mechanism in place if in general an unhandled exception (from a background thread or not) needs to be handled in the App class of my application.
**EDIT / UPDATE**
I changed the way how the dialog is shown using the DispatcherQueue class like indicated in this post.
This got rid of the exception when trying to show the dialog without dispatching it properly to the UI thread.
However it seems that if the exception comes from a background thread and not caught there, the App class cannot or is not supposed to prevent the application from shutting down.
That is why the application is already closed before any dialog is shown, I presume.
See this Exceptions in managed threads article: "When threads are allowed to fail silently, without terminating the application, serious programming problems can go undetected. This is a particular problem for services and other applications that run for extended periods. As threads fail, program state gradually becomes corrupted. Application performance may degrade, or the application might become unresponsive."
In addition the IsTerminating property of my UnhandledExceptionEventArgs instance true.
As far as I know, the default exception handling for unhandled exceptions is to terminate the application that is confirmed by Application.UnhandledException Event and also explains why there is no dialog.
With respect to your specific exception, the handling of the exception should not be terminating the application. You are safe to show dialog with message on the UI thread. A possible handling could be showing dialog with message (logging if you would), resuming execution and retrying properly a few seconds later.
Also note,
And
However it seems that if the exception comes from a background thread and not caught there, the App class cannot or is not supposed to prevent the application from shutting down.
No, It can.