0

I have a WinForms app that contains a single form with a background worker. The form contains a button that starts the background worker via RunWorkerAsync() and another button that will exit the application. About 1/3 of the time, after the background worker has completed its work, the application will crash after I click on the Exit button with an exception like this:

System.NullReferenceException was unhandled
  Message=Object reference not set to an instance of an object.
  Source=System.Drawing
  StackTrace:
       at System.Drawing.Graphics.Dispose(Boolean disposing)
       at System.Drawing.Graphics.Finalize()

Here is the event handler for the button that exits the application:

    private void buttonExit_Click(object sender, EventArgs e)
    {
        if (!buttonStartWorker.Enabled)
        {
            DialogResult dr = MessageBox.Show("Background worker is still running!  Exit anyway?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);

            if (dr == DialogResult.OK)
            {
                backgroundWorker.CancelAsync();
                Close();
            }
        }
        else
        {
            Close();
        }
    }

As I said earlier, I'm not exiting the application while the background worker is still running so the code path we're looking at here is just the Close() call. There's also a FormClosing event handler which calls close and dispose methods on my USB-related handles. That code is as follows:

    private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        try
        {
            // close and dispose all open handles to the USB device
            if (hidHandle != null)
            {
                if (!(hidHandle.IsInvalid))
                {
                    hidHandle.Close();
                    hidHandle.Dispose();
                }
            }

            if (readHandle != null)
            {
                if (!(readHandle.IsInvalid))
                {
                    readHandle.Close();
                    readHandle.Dispose();
                }
            }

            if (writeHandle != null)
            {
                if (!(writeHandle.IsInvalid))
                {
                    writeHandle.Close();
                    writeHandle.Dispose();  // unhandled exception seems to occur after this
                }
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
        }
    }

Sometime between writeHandle.Dispose() and the time the application actually exits, this exception is occurring. The thing that's confusing me the most is the fact that my code never explicitly makes use of System.Drawing so I'm having trouble tracking this down.

For what it's worth, my background worker does the following:

  1. It reads and writes some data to/from a USB device
  2. It creates a web client to download some data
  3. It makes a few SOAP calls

Does anyone have any ideas on what could be causing an unhandled NullReferenceException in System.Drawing when an application (that doesn't explicitly use System.Drawing) exits?

bmt22033
  • 6,880
  • 14
  • 69
  • 98

2 Answers2

1

Although KeithS' answer is correct in principal, your code does seem to have a visible problem. You are calling this.Close(); right after a call to CancelAsync(). I would try to either wait for the background worker to finish its business or subscribe to a Cancelled event if one exists.

It is most likely that your background worker has not finished. Also note that a background worker is a component that ties into the Form's event heirarchy.

Try creating a new Task:

this.BeginInvoke(new Action(() => (Thread.Sleep(1000); this.Close();)));.

Sorry about the incorrect syntax but I am not on a dev machine.

Raheel Khan
  • 14,205
  • 13
  • 80
  • 168
  • Thanks for the suggestion, Raheel. You certainly make a valid point with regard to the code path that calls Close() immediately after CancelAsync(). Unfortunately, that's not the code path that I've been executing so far. I haven't been closing the app while the background worker is still running. In my backgroundWorker_RunWorkerCompleted() method (not shown here), I set buttonStartWorker.Enabled = true which is causing the else clause inside buttonExit_Click() to be executed. I've verified this by putting a breakpoint on the first line of buttonExit_Click(). – bmt22033 Apr 27 '12 at 19:49
0

While your program may not use System.Drawing explicitly, that namespace is all over most WinForms object code. It appears that somewhere in your form, one GUI object is given a reference to another object's Graphics handle, which it then thinks it has to destroy; but, the control that owns that handle has already been disposed, or the Graphics object itself has had its Dispose method called explicitly, and so the Dispose() code fails when run the second time. I would have expected the .NET developers to put in checks to make sure a double-Dispose wouldn't be a problem, but it appears in this case they overlooked it.

Without knowing exactly what you have on your windows form, and exactly which instance of System.Drawing.Graphics is throwing out, I can't really help further. That would be where I'd investigate further; try to discover the exact instance being disposed, what other object owns it, and why it's being finalized after already being disposed (usually if explicitly disposed, a call to GC.SuppressFinalize() should be made).

One of the "reflected" codebase references may help you out; look for System.Drawing.Graphics.Dispose(bool disposing), and browse for any line of code that would fail if run twice in a row. That may give you a hint.

KeithS
  • 70,210
  • 21
  • 112
  • 164