ChatGPT解决这个技术问题 Extra ChatGPT

Globally catch exceptions in a WPF application?

We are having a WPF application where parts of it may throw exceptions at runtime. I'd like to globally catch any unhandled exception and log them, but otherwise continue program execution as if nothing happened (kinda like VB's On Error Resume Next).

Is this possible in C#? And if so, where exactly would I need to put the exception handling code?

Currently I can't see any single point where I could wrap a try/catch around and which would catch all exceptions that could occur. And even then I would have left whatever has been executed because of the catch. Or am I thinking in horribly wrong directions here?

ETA: Because many people below pointed it out: The application is not for controlling nuclear power plants. If it crashes it's not that much a big deal but random exceptions that are mostly UI-related are a nuisance in the context where it would be used. There were (and probably still are) a few of those and since it uses a plugin architecture and may be extended by others (also students in that case; so no experienced developers that are able to write completely error-free code).

As for the exceptions that get caught: I do log them to a log file, including the complete stack trace. That was the whole point of that exercise. Just to counter those people that were taking my analogy to VB's OERN too literally.

I know that blindly ignoring certain classes of errors is dangerous and might corrupt my application instance. As said before, this program isn't mission-critical for anyone. No-one in their right mind would bet the survival of the human civilization on it. It's simply a little tool for testing certain design approaches wrt. software engineering.

For the immediate use of the application there are not many things that can happen on an exception:

No exception handling – error dialog and application exit. Experiment has to be repeated, though likely with another subject. No errors have been logged, which is unfortunate.

Generic exception handling – benign error trapped, no harm done. This should be the common case judged from all errors we were seeing during development. Ignoring this kind of errors should have no immediate consequences; the core data structures are tested well enough that they will easily survive this.

Generic exception handling – serious error trapped, possibly crash at a later point. This may happen rarely. We've never seen it so far. The error is logged anyway and a crash might be inevitable. So this is conceptually similar to the very first case. Except that we have a stack trace. And in the majority of cases the user won't even notice.

As for the experiment data generated by the program: A serious error would at worst just cause no data to be recorded. Subtle changes that change the result of the experiment ever so slightly are pretty unlikely. And even in that case, if the results seem dubious the error was logged; one can still throw away that data point if it's a total outlier.

To summarize: Yes, I consider myself still at least partially sane and I don't consider a global exception handling routine which leaves the program running to be necessarily totally evil. As said twice before, such a decision might be valid, depending on the application. In this case it was judged a valid decision and not total and utter bullshit. For any other application that decision might look different. But please don't accuse me or the other people who worked on that project to potentially blow up the world just because we're ignoring errors.

Side note: There is exactly one user for that application. It's not something like Windows or Office that gets used by millions where the cost of having exceptions bubble to the user at all would be very different in the first place already.

This not really thinking--yes, you probably want the application to exit. BUT, wouldn't be nice to first log the exception with its StackTrace? If all you got from the user was, "Your application crashed when I pressed this button", you may never be able to resolve the issue because you wouldn't have sufficient information. But if you first logged the exception before more pleasantly aborting the application, you will have significantly more information.
I elaborated that point a little in the question now. I know the risks involved and for that particular application it was deemed acceptable. And aborting the application for something as simple as an index out of bounds while the UI tried to do some nice animation is overkill and unneeded. Yes, I don't know the exact cause but we have data to back up the claim that the vast majority of error cases is benign. The serious ones we're masking might cause the application to crash but that's what would have happened without global exception handling anyway.
Another side note: if you prevent any crashes with this approach, the users will most likely love it.
See Windows Handling Unhandled Exceptions in WPF (The most complete collection of handlers) sample in C# for Visual Studio 2010. It has 5 examples including AppDomain.CurrentDomain.FirstChanceException, Application.DispatcherUnhandledException and AppDomain.CurrentDomain.UnhandledException.
I would like to add that VB-like code-flow of On Error Resume Next is not possible in C#. After an Exception (C# doesn't have "errors") you cannot simply resume with the next statement: execution will continue in a catch block - or in one of the event handlers described in the answers below.

C
Community

Use the Application.DispatcherUnhandledException Event. See this question for a summary (see Drew Noakes' answer).

Be aware that there'll be still exceptions which preclude a successful resuming of your application, like after a stack overflow, exhausted memory, or lost network connectivity while you're trying to save to the database.


If your exception occurs on a background thread (say, using ThreadPool.QueueUserWorkItem), this will not work.
@PitiOngmongkolkul: The handler is called as event from you main loop. When the event handlers returns, your app continues normally.
It seems that we need to set e.Handled = true, where e is an DispatcherUnhandledExceptionEventArgs, to skip the default handler which quit the programs. msdn.microsoft.com/en-us/library/…
@PitiOngmongkolkul The answer should be edited to take into account e.Handled =true
This solution on it's own may not be enough to catch all exceptions. It is very likely that background threads could fail in a bad enough way to crash your app, I recommend also implementing @charithj solution with AppDomain.UnhandledException or using LegacyUnhandledExceptionPolicy...
H
Hüseyin Yağlı

Example code using NLog that will catch exceptions thrown from all threads in the AppDomain, from the UI dispatcher thread and from the async functions:

App.xaml.cs :

public partial class App : Application
{
    private static Logger _logger = LogManager.GetCurrentClassLogger();

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        SetupExceptionHandling();
    }

    private void SetupExceptionHandling()
    {
        AppDomain.CurrentDomain.UnhandledException += (s, e) =>
            LogUnhandledException((Exception)e.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException");

        DispatcherUnhandledException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "Application.Current.DispatcherUnhandledException");
            e.Handled = true;
        };

        TaskScheduler.UnobservedTaskException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
            e.SetObserved();
        };
    }

    private void LogUnhandledException(Exception exception, string source)
    {
        string message = $"Unhandled exception ({source})";
        try
        {
            System.Reflection.AssemblyName assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName();
            message = string.Format("Unhandled exception in {0} v{1}", assemblyName.Name, assemblyName.Version);
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Exception in LogUnhandledException");
        }
        finally
        {
            _logger.Error(exception, message);
        }
    }

This is most complete answer here! Taks scheduler exceptions are included. Best for me, clean and simple code.
Recently I had an App.config corruption at a customer, and her App wasn't even starting because NLog was trying to read from App.config and threw an exception. Since that exception was in the static logger initializer, it wasn't being caught by the UnhandledException handler. I had to look at Windows Event Log Viewer to find what was happening...
I reccomend to set e.Handled = true; at the UnhandledException that the application will not crash on UI Exceptions
C
CharithJ

AppDomain.UnhandledException Event

This event provides notification of uncaught exceptions. It allows the application to log information about the exception before the system default handler reports the exception to the user and terminates the application.

   public App()
   {
      AppDomain currentDomain = AppDomain.CurrentDomain;
      currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);    
   }

   static void MyHandler(object sender, UnhandledExceptionEventArgs args) 
   {
      Exception e = (Exception) args.ExceptionObject;
      Console.WriteLine("MyHandler caught : " + e.Message);
      Console.WriteLine("Runtime terminating: {0}", args.IsTerminating);
   }

If the UnhandledException event is handled in the default application domain, it is raised there for any unhandled exception in any thread, no matter what application domain the thread started in. If the thread started in an application domain that has an event handler for UnhandledException, the event is raised in that application domain. If that application domain is not the default application domain, and there is also an event handler in the default application domain, the event is raised in both application domains. For example, suppose a thread starts in application domain "AD1", calls a method in application domain "AD2", and from there calls a method in application domain "AD3", where it throws an exception. The first application domain in which the UnhandledException event can be raised is "AD1". If that application domain is not the default application domain, the event can also be raised in the default application domain.


copying from the URL you pointed to (which speaks of console apps and WinForms apps only I think): "Starting with the .NET Framework 4, this event is not raised for exceptions that corrupt the state of the process, such as stack overflows or access violations, unless the event handler is security-critical and has the HandleProcessCorruptedStateExceptionsAttribute attribute."
@GeorgeBirbilis: You can subscribe for UnhandledException event in the App constructor.
V
Vadim Ovchinnikov

In addition what others mentioned here, note that combining the Application.DispatcherUnhandledException (and its similars) with

<configuration>
  <runtime>  
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

in the app.config will prevent your secondary threads exception from shutting down the application.


N
NoWar

Here is complete example using NLog

using NLog;
using System;
using System.Windows;

namespace MyApp
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public App()
        {
            var currentDomain = AppDomain.CurrentDomain;
            currentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var ex = (Exception)e.ExceptionObject;
            logger.Error("UnhandledException caught : " + ex.Message);
            logger.Error("UnhandledException StackTrace : " + ex.StackTrace);
            logger.Fatal("Runtime terminating: {0}", e.IsTerminating);
        }        
    }


}

B
BobbyShaftoe

Like "VB's On Error Resume Next?" That sounds kind of scary. First recommendation is don't do it. Second recommendation is don't do it and don't think about it. You need to isolate your faults better. As to how to approach this problem, it depends on how you're code is structured. If you are using a pattern like MVC or the like then this shouldn't be too difficult and would definitely not require a global exception swallower. Secondly, look for a good logging library like log4net or use tracing. We'd need to know more details like what kinds of exceptions you're talking about and what parts of your application may result in exceptions being thrown.


The point is that I want to keep the application running even after uncaught exceptions. I just want to log them (we have a custom logging framework for this, based on our needs) and don't need to abort the whole program just because some plugin did something weird in its user interaction code. From what I've seen so far it's not trivial to isolate the causes cleanly as the different parts do not really know each other.
I understand but you have to be very cautious of continuing after an exception that you do not examine, there is the possibility of data corruption and so forth to consider.
I agree with BobbyShaftoe. This is not the correct way to go. Let the application crash. If the fault is on some plugin, then fix or get somebody to fix the plugin. Leaving it running is EXTREMELY dangerous. You will get weird side effects and will reach a point where you won't be able to find a logical explanation for the bugs happening in your application.
I elaborated that point a little in the question now. I know the risks involved and for that particular application it was deemed acceptable. And aborting the application for something as simple as an index out of bounds while the UI tried to do some nice animation is overkill and unneeded. Yes, I don't know the exact cause but we have data to back up the claim that the vast majority of error cases is benign. The serious ones we're masking might cause the application to crash but that's what would have happened without global exception handling anyway.
Conversely, you could put each plugin into its own app-domain, so you could catch and log its exceptions, then let that one domain crash without crashing your whole app. BUT it does mean trickier plugin<=>app communication. In most cases, the benefit is not worth the extra work.