Click here to Skip to main content
15,886,724 members
Articles / Desktop Programming / Windows Forms
Article

How to close a multi-threaded .NET Windows Forms application and prevent the ObjectDisposedException from getting thrown

Rate me:
Please Sign up or sign in to vote.
3.50/5 (3 votes)
15 Nov 2008LGPL34 min read 67.8K   897   20   7
An article on how to properly close a multi-threaded Windows Forms application without having an ObjectDisposedException thrown.

Assumptions

Knowledge of .NET Windows Forms event handling and threading is assumed, including the use of the lock statement, and how to invoke events onto a GUI thread.

Overview

This article explains how to properly close a multi-threaded .NET Windows Forms application where there is a thread running in the background that fires events which update or modify the GUI. The main problem that can arise if such an application is not closed down properly (via code) is that an ObjectDisposedException may get thrown with the message, "Cannot access a disposed object". This error occurs because the GUI gets disposed of first before the background thread has a chance to close, causing an event to get fired which tries to update the disposed object. The key things leading to the problem are the GUI getting disposed of first before the background thread, and the background thread firing events that update the GUI. Note that if it so happens that the background thread closes first before the GUI, then this problem doesn’t arise, which is why you may have noticed that this problem doesn’t occur consistently every time.

The following is a diagram of what the problem looks like:

Problem_ThreadDiagram.JPG

Problem

The following code shows how to reproduce the problem.

C#
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    // Signal the BackgroundWorkerThread to end
    this.CloseBackgroundWorker = true;
}
void BackgroundWorkerThread()
{
    // Check to see if the background worker is signaled
    // to end
    while (this.CloseBackgroundWorker != true)
    {
        // Fire an event that updates the GUI
        SomeEvent(null, null);

        // Timeout (kept small on purpose)
        Thread.Sleep(1);
    }
}
void Form1_SomeEvent(object sender, EventArgs e)
{
    if (this.InvokeRequired == true)
    {
        // Invoke the event onto the GUI thread
        EventHandler eh = new EventHandler(Form1_SomeEvent);
        this.Invoke(eh, new object[] { null, null });
    }
    else
    {
        // Event handler code that updates the GUI
        label1.Text = "Counter value: " + _counter++;
    }
}

Basically, when the user tells the application to close, the thread running in the background, BackgroundWorkerThread, gets signaled to close, and the GUI, Form1, gets disposed of. However, and here’s the problem, there’s no guarantee that BackgroundWorkerThread will close first before Form1 is disposed of. This is a big problem, because if BackgroundWorkerThread runs for even a short amount of time after Form1 has been disposed of, there’s a good chance that the event that updates the GUI, SomeEvent, will get fired (see label1.Text getting changed in the Form1_SomeEvent event handler). And, because Form1 at this point has already been disposed of, it’s not possible to update it, and an ObjectDisposedException exception will get thrown.

To reproduce this behavior, simply download, build, and run the application, then click the exit button to close the application. The exception may not get thrown every time, as noted earlier, so simply run the application, and try again if nothing happens. Note, the while loop in BackgroundWorkerThread has a very small timeout in order to increase the odds for the exception to get thrown.

Solution

The solution to this problem is pretty simple in concept. When the user attempts to close the GUI, the GUI should first wait until the thread running in the background closes, and then, only after that has happened, proceed in shutting itself down. So, in the example code shown above, Form1 should first wait until BackgroundWorkerThread has closed, and only when that has happened, proceed in closing itself down.

The following is a diagram of the solution:

Solution_ThreadDiagram.JPG

Here is the code for the solution:

C#
bool _safeToCloseGUI = false;
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    // Signal the BackgroundWorkerThread to end
    this.CloseBackgroundWorker = true;

    // The _safeToCloseGUI flag will  be true when the
    // background worker thread has finished
    if (_safeToCloseGUI == false)
    {
        // The background worker thread has not closed yet,
        // so don't close the GUI yet
        e.Cancel = true;
    }
}

void BackgroundWorkerThread()
{
    // Check to see if the background worker is signaled
    // to end
    while (this.CloseBackgroundWorker != true)
    {
        // Fire an event that updates the GUI
        SomeEvent(null, null);

        // Timeout (kept small on purpose)
        Thread.Sleep(1);
    }

    // If we get here, it means that the background worker
    // thread is finished doing its work. It is now safe
    // to shutdown the GUI (since SomeEvent cannot be called
    // anymore). The GUI must be closed via an event because
    // this code is running in a background thread.

    // Fire the event to close the GUI
    CloseGUI(null, null);
}

void Form1_SomeEvent(object sender, EventArgs e)
{
    if (this.InvokeRequired == true)
    {
        // Invoke the event onto the GUI thread
        EventHandler eh = new EventHandler(Form1_SomeEvent);
        this.Invoke(eh, new object[] { null, null });
    }
    else
    {
        // Event handler code that updates the GUI
        label1.Text = "Counter value: " + _counter++;
    }
}
void Form1_CloseGUI(object sender, EventArgs e)
{
    if (this.InvokeRequired == true)
    {
        // Invoke the event onto the GUI thread
        EventHandler eh = new EventHandler(CloseGUI);
        this.Invoke(eh, new object[] { null, null });
    }
    else
    {
        // Event handler code that tells the GUI it is
        // safe to close, then closes it
        _safeToCloseGUI = true;
        this.Close();
    }
}

A few changes have been made to the original code.

First, a new flag has been added to the Form1_FormClosing method, _safeToCloseGUI, that allows the GUI to be closed only when the flag is set to true. When the user clicks to close the application, the background worker thread will be signaled to close just like before. However, now the GUI’s close operation will be canceled out because the _safeToCloseGUI flag is set to false initially. As will be seen next, this flag will be set to true once it is safe to close down the GUI.

Next, BackgroundWorkerThread has been modified so that once it is signaled to close down, it will break out of its while loop, and then afterwards fire an event that tells the GUI to close down. After BackgroundWorkerThread has broken out of its while loop, it is guaranteed to not fire the event that updates the GUI, and therefore at this point, it is safe for the GUI to shutdown. Therefore, BackgroundWorkderThread will fire the CloseGUI event to tell the GUI to shut down.

Finally, the event handler Form1_CloseGUI has been added to handle the CloseGUI event. First, the handler sets the _safeToCloseGUI flag to true, and then it calls the Close method. This will cause Form1_FormClosing to continue because _safeToCloseGUI is now true, and the GUI will safely and successfully shut down.

Alternate Approach

The following is another way to solve this issue with a lot less code, albeit the solution is not as elegant. Basically, a try/catch can be placed around the invoke line where the GUI is updated so that if an exception is thrown, it will get handled and the application will not crash. See the following code:

C#
void Form1_SomeEvent(object sender, EventArgs e)
{
    if (this.InvokeRequired == true)
    {
        // Invoke the event onto the GUI thread
        EventHandler eh = new EventHandler(Form1_SomeEvent);
        try
        {
            this.Invoke(eh, new object[] { null, null });
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }
    else
    {
        // Event handler code that updates the GUI
        label1.Text = "Counter value: " + _counter++;
    }
}

This approach is not as elegant because an exception still occurs upon exit of the application (even though it is handled and there’s no crash). However, if you want to avoid the exception altogether, the recommended approach is the originally proposed solution. The original solution might be a little bit more work, but I would argue that it is really the “cleaner” solution.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
United States United States
If you would like to simplify your client server programming, check out Tangent API.

Comments and Discussions

 
GeneralShort solution with BackgroundWorker Pin
Dark Daskin24-Feb-10 12:35
Dark Daskin24-Feb-10 12:35 
Everything in one method without additional flags. foreach can be removed if you have only one worker. May be useful for someone.
C#
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    foreach (var worker in new[] { searchWorker, progressWorker })
        if (worker.IsBusy)
        {
            worker.RunWorkerCompleted += (_sender, _e) => 
                Close();
            worker.CancelAsync();
            e.Cancel = true;
        }
}

GeneralOne thing to add... Pin
nhumbad19-Nov-08 3:56
nhumbad19-Nov-08 3:56 
GeneralProblems Pin
merlin98116-Nov-08 1:04
professionalmerlin98116-Nov-08 1:04 
GeneralLarger issue Pin
Itay Sagui15-Nov-08 21:39
Itay Sagui15-Nov-08 21:39 
QuestionWhat do I miss? Pin
Chris Richner15-Nov-08 8:07
Chris Richner15-Nov-08 8:07 
AnswerRe: What do I miss? Pin
nhumbad15-Nov-08 15:19
nhumbad15-Nov-08 15:19 
GeneralRe: What do I miss? Pin
Chris Richner16-Nov-08 4:54
Chris Richner16-Nov-08 4:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.