Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Application.ThreadException event for ALL GUI threads

I have a WinForms application that creates a multiple forms, each in their own GUI thread, (not the main GUI thread). I would like to handle the Unhandled Exception event (Application.ThreadException) for all these forms to handle any errors. I would also like to handle exceptions from worker threads - this bit seems to be working correctly, but I'm having trouble with exceptions from GUI threads still:

Program.cs:

[STAThread]
static void Main()
{
  AttachExceptionHandlers();
  Application.EnableVisualStyles();
  Application.SetCompatibleTextRenderingDefault(false);
  Application.Run(new Form1());
}
public static void AttachExceptionHandlers()
{
  Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
  Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);      
  AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
  Dispatcher.CurrentDispatcher.UnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(CurrentDispatcher_UnhandledException);
}

Form1.cs:

//GUI Thread Crash
private void button1_Click(object sender, EventArgs e)
{
  object a = null;
  a.ToString();
}
//Worker Thread Crash
private void button2_Click(object sender, EventArgs e)
{
  Thread myThread = new Thread(() => 
  { 
    object a = null; 
    a.ToString(); 
  });
  myThread.Start();
  myThread.Join();
}
//New Thread, New Gui Crash
private void button3_Click(object sender, EventArgs e)
{
  Thread myThread = new Thread(() => 
  { 
    using (CrashingForm form = new CrashingForm()) //Crashing Form Crashes in it's FormLoad event.
    {
      Application.Run(form);
    }
  });
  myThread.Start();
  myThread.Join();
}

This code will call my exception handler in the first 2 instances (GUI Thread Crash and Worker Thread Crash) but does not handle the third instance where a new GUI thread is created. I have found that if I call Program.AttachExceptionHandlers(); before the Application.Run(form) line, all is OK, but this is undesirable as I would have to implement some logic to make sure the call to Program.AttachExceptionHandlers() is made before we call a form is created on each thread (the call to Application.SetUnhandledExceptionMode fails if called after creating a form on the thread).

This example is part of a bigger bit of code which would ideally give the user of my code a simple API to call at the start of their application (like in Program.cs) to attach exception handlers. The exception handler then does some magic to record details about the exception being thrown before the application dies. So telling the user they have to track down each time they create a new GUI thread (worker threads don't seem to be effected by this issue) and reattach the Application.ThreadException Handler is not such a clean solution.

Is there another way to achieve this, without having to re registerer for the Application.ThreadException event each time a new GUI thread is created?

like image 472
rb_ Avatar asked Nov 01 '22 12:11

rb_


1 Answers

Is there another way to achieve this, without having to re registerer for the Application.ThreadException event each time a new GUI thread is created?

I'm not aware of any, and my teammate and I have spent a good amount of time looking into it. .NET WinForms doesn't appear to be very opinionated when it comes to how to create / manage / destroy multiple message pumps.

We use framework methods similar to the one below, in addition to Retlang's WinFormsFiber.

using System;
using System.Threading;
using System.Windows.Forms;

internal static class Program
{
    [STAThread]
    private static void Main()
    {
        CreateFormAndStartMessagePump(() => CreateForm("first"), OnException, OnException, false, "pumpThread1");
        CreateFormAndStartMessagePump(() => CreateForm("second"), OnException, OnException, false, "pumpThread2");
        // note app shutdown not handled here
    }

    private static T CreateFormAndStartMessagePump<T>(
        Func<T> createForm,
        ThreadExceptionEventHandler onThreadException,
        UnhandledExceptionEventHandler onDomainException,
        bool isBackground,
        string name) where T : Form
    {
        var latch = new ManualResetEvent(false);
        T form = null;
        var thread = new Thread(ts =>
        {
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
            Application.ThreadException += onThreadException;
            AppDomain.CurrentDomain.UnhandledException += onDomainException;
            form = createForm();
            latch.Set();
            Application.Run();
        })
        {
            IsBackground = isBackground,
            Name = name
        };
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        latch.WaitOne();
        return form;
    }

    private static Form CreateForm(string name)
    {
        var form = new Form();
        form.Text = name;
        form.Show();
        return form;
    }

    private static void OnException(object sender, UnhandledExceptionEventArgs e)
    {
        // ...
    }

    private static void OnException(object sender, ThreadExceptionEventArgs e)
    {
        // ...
    }
}
like image 119
Garrett Smith Avatar answered Nov 15 '22 05:11

Garrett Smith