Using MessageBox to show exception information in

2019-08-02 18:06发布

问题:

Hi I'm working with winform and trying to use MessageBox for exception handling. The weird thing here is, the MessageBox appears only after the main form ("Form1" in the code below) is closed.

public class Worker {
    /* edited (see below)
    public void doWork() {
        try {
            // do something
            client.Connect(serverAddress);
            stream = client.GetStream();
        }
        catch(Exception e) {
            MessageBox.Show(e.ToString(), 
                "This will not show up until Form1 is closed");
        }
    }
    */
}

public class Form1 {
    /* edited (see below)
     * public void threadProc() {
     *    Worker worker = new Worker();
     *    worker.doWork();
     * }
     */
     void button_Click(object sender, EventArgs e) {
        // create a thread that will end up throwing an exception
        Thread thread = new Thread(threadProc);
        thread.Start();
     }
}

What could be a better way to use MessageBox for exception handling?


...So I added some codes for MessageBox-ing in the UI thread, but the problem remains.

public class WorkExceptionArgs : EventArgs {
    public Exception e;
    public WorkExceptionArgs (Exception e) { this.e = e; }
}
public partial class Worker1 { // renamed (Worker->Worker1)
    /* (edited) Now Worker1 doesn't trigger any event (see below)
       public event EventHandler<WorkExceptionArgs> workException;
    */
    public void doWork() {
        try {
            // do something
            client.Connect(serverAddress);
            stream = client.GetStream();
        }
        catch(Exception e) {
            /* (edited) suppose Worker1 never throws any exception (see below)
             *  // trigger event that will cause MessageBox-ing by UI thread
             *  workException(this, new WorkExceptionArgs(e));
             */
        }
    }
}
public partial class Form1 {
    public void threadProc() {
       Worker1 worker1 = new Worker();
      /* (edited) Now Worker1 never throws any exception
       * worker.workException += new EventHandler<WorkException>(worker_WorkException);
       */
       worker1.doWork();
       // (added) After doWork() is done, Form1 creates Worker2
       Worker2 w2 = new Worker2(this, this.form2);
       w2.workException += new EventHandlerArgs<WorkExceptionArgs>(form2.worker2_WorkException);
       w2.doSomeOtherWork();
    }
    /* public void worker_WorkException(object sender, WorkExceptionArgs eArg) {
     *   MessageBox.Show(eArg.e.ToString(), "Still not showing");
     * } */
    Form2 form2 = new Form2(); // (added) At first form2 is hidden (see below)
}

Actually there have been another form and another worker. Once Worker(Worker1) made connection to the server, Form1 hides (.Hide()), Form2 shows (.Show()), and Worker2 starts working with the connection Worker1 made.

public class Worker2 {
    Worker2(Worker1 w1, Form2 frm2) { this.w1=w1; this.frm2=frm2; }
    public Worker1 w1;
    public Form2 frm2;
    public event EventHandler<WorkExceptionArgs> workException;
    public void doSomeOtherWork() { // do some other, using data in Worker 1.
        try { // This will throw an exception
            BinaryFormatter formatter = new BinaryFormatter();
            MyObj mo = (MyObj)formatter.Deserialize(w1.getStream());
        }
        catch(Exception e) {
            workException(this, new WorkExceptionArgs(e));
        }
    }             
}

public class Form2 {
    public Form2(Form1 frm1) { // to switch from frm1 to frm2
        InitializeComponent();
        this.frm1 = frm1;
    }
    public Frm1 frm1 {get;set;}
    public void worker2_WorkException(object sender, WorkExceptionArgs ea) {
       MessageBox.Show(this, ea.e.ToString(), "SHOWS ONLY IF FORM2 IS CLOSED");
    }

}     

public partial class Form1 {
    delegate void switchWindow_Callback();
    public void switchWindow() { this.Hide(); form2.Show(); }
    public void switchWindowCb(object sender, EventArgs e) {
        if(this.InvokeRequired) {
            SwitchWindow_Callback hcb = new SwitchWindow_Callback(switchWindow);
            this.Invoke(hcb, new object[] {});
        }
        else { this.switchWindow(); }
    }
}

回答1:

Actually I'll bet the MessageBox is appearing behind the main form, and you just don't see it until you close it.

You'd be much better off letting the UI thread (the one that created and owns Form1) do the MessageBox-ing. You either want to make events, or otherwise have an error callback delegate in your worker class.

However, BackgroundWorker may be worth checking out here rather than trying to roll your own. Assuming it's a fatal exception, you can save and retrieve the error state, and you get an event automatically called when the thread finishes.



回答2:

You should really be locking down the doWork method so that multiple threads cant access it at the same time, they need to queue.

Look into "Joining Threads". Imagine if you get two exception at the same time. Your app will fall over. Locking down the area of code that will be duplicated repeatedly will form a queue for your threads to access the area of code that handles the exception.



回答3:

As lc stated, it's quite likely that your message box is appearing behind the main form, and therefore you only see it when the main form is closed.

The model I use to deal with unhandled exceptions in a Windows Forms app looks like this:

// Switch-off the Windows Forms default handler for unhandled exceptions.
// NB From .NET 4 upwards, this won't work if the process state is corrupted.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

// Setup event handler to intercept an unhandled exception on a UI thread .
// NB The exception will still terminate the application. 
// But you can show a MessageBox in the event handler and log the exception. 
Application.ThreadException += 
    new ThreadExceptionEventHandler(App_UiThreadException);

// Setup event handler to intercept an unhandled exception on a non-UI thread.
AppDomain.CurrentDomain.UnhandledException += new 
    UnhandledExceptionEventHandler(App_NonUiThreadException);

// Run the application (open main form etc).

The first line says that you want to catch any unhandled exception and deal with it yourself rather than letting the WinForms infrastructure deal with it. Note that from .NET 4 onwards, this setting won't work for an exception that corrupts process state (for example OutOfMemory).

If you have an unhandled exception on a UI thread, the second line will trigger a procedure you create called *App_UiThreadException*. That's where your MesssageBox code should go.

If you have an unhandled exception on a non-UI thread, the last line will trigger a procedure you create called *App_NonUiThreadException*. That's where your MesssageBox code should go.