AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.42k stars 2.2k forks source link

Dialogs shown via AppDomain.CurrentDomain.UnhandledException do not render #5387

Open derekantrican opened 3 years ago

derekantrican commented 3 years ago

Describe the bug

Here is my example (full example: AvaloniaDialogTest.zip):

Program.cs:

namespace AvaloniaDialogTest
{
    class Program
    {
        public static void Main(string[] args)
        {
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

            BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
        }

        private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            if (e.ExceptionObject is Exception)
            {
                Window parentWindow = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).MainWindow;
                new Dialog().ShowDialog(parentWindow).Wait();
            }
        }

        public static AppBuilder BuildAvaloniaApp()
            => AppBuilder.Configure<App>()
                .UsePlatformDetect()
                .LogToTrace();
    }
}

Showing a dialog via unhandled exception gives an empty, blank dialog (the dialog also can't be interacted with or closed): image

Normally, the dialog looks like this: image

To Reproduce Steps to reproduce the behavior:

  1. Use the above code or reference the attached example

Desktop (please complete the following information):

jp2masa commented 3 years ago

The Wait() on this line blocks:

new Dialog().ShowDialog(parentWindow).Wait();

So the app "crashes". You will have to either not wait, and simply show the dialog:

new Dialog().ShowDialog(parentWindow);

Or make the method async, which assuming it won't throw exceptions shouldn't be a problem:

private static async void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
await new Dialog().ShowDialog(parentWindow);
derekantrican commented 3 years ago

I'm aware that I'm blocking the current thread by using .Wait() but, again, this used to work in 0.9.12. I haven't tested this, but I would imagine that what happened is AppDomain.CurrentDomain.UnhandledException got moved to the UI thread.

I'll try your suggestions

derekantrican commented 3 years ago

@jp2masa Here is the issue with your suggestions: by the definition of being in the unhandled exception method, the app is in the process of crashing. Therefore, removing Wait() or using async....await means that my dialog doesn't get shown and the app crashes (I've tested both)

jp2masa commented 3 years ago

You can always try this workaround: https://github.com/AvaloniaUI/Avalonia/issues/4810#issuecomment-704259221. Alternatively, as the main thread is crashing, I think the only reliable way to show the dialog would probably be on another thread, but that would require setting up Avalonia for that thread which would be a lot of work.

derekantrican commented 3 years ago

That solution seems to work on Windows, but I remember there being issues with using MainLoop on Mac: #5229. I'll have to test this there

derekantrican commented 3 years ago

Yeah, confirmed: the #4810 workaround doesn't work for MacOS

derekantrican commented 3 years ago

I have updated the title and description because after some reverting and testing, I don't know if this truly worked in 0.9.12. It's probably that I tried the 4810 workaround and that worked for unhandled exceptions. But when I realized that the 4810 workaround doesn't work on MacOS and updated my code, it's possible I didn't check dialogs from unhandled exceptions

maxkatz6 commented 3 years ago

@derekantrican not an advice, but I know some applications/games start new process for handling unhandled exceptions and showing user detailed information with asking for a next steps (restart app, send telemetry...).

derekantrican commented 3 years ago

Right, that's what I'm doing in my full app (asking for more info, sending logs, etc) - the Dialog is just an example.

I'd like to look into that - do you know how I could implement that (or have an example to point me to)? I'm only familiar with using AppDomain.CurrentDomain.UnhandledException

maxkatz6 commented 3 years ago

do you know how I could implement that (or have an example to point me to)?

If you are asking about examples with separated process for error handling, I don't know any in open source, haven't looked for them before.

pr8x commented 3 years ago

@derekantrican For native code there's google breakpad, which helps with implementing a OOP crash handling infrastructure. I haven't seen anything for C# though. OOP is usually required when dealing with errors (like StackOverflow, AccessViolation) that corrupt the program state. These errors aren't caught by AppDomain.UnhandledException though (unless you explicitely ask for it with the HandleProcessCorruptedStateExceptionsAttribute) It should be relatively easy to implement: The client process (i.e. your application) is sending information about the error (i.e. the serialized Exception object) to the server process using some form of IPC (Look for Named Pipes on Windows). The crash server would then present this information in some way or start writing some sort of crash dump which can be send back to the developer.