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
The following code shows how to reproduce the problem.
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
this.CloseBackgroundWorker = true;
}
void BackgroundWorkerThread()
{
while (this.CloseBackgroundWorker != true)
{
SomeEvent(null, null);
Thread.Sleep(1);
}
}
void Form1_SomeEvent(object sender, EventArgs e)
{
if (this.InvokeRequired == true)
{
EventHandler eh = new EventHandler(Form1_SomeEvent);
this.Invoke(eh, new object[] { null, null });
}
else
{
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:

Here is the code for the solution:
bool _safeToCloseGUI = false;
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
this.CloseBackgroundWorker = true;
if (_safeToCloseGUI == false)
{
e.Cancel = true;
}
}
void BackgroundWorkerThread()
{
while (this.CloseBackgroundWorker != true)
{
SomeEvent(null, null);
Thread.Sleep(1);
}
CloseGUI(null, null);
}
void Form1_SomeEvent(object sender, EventArgs e)
{
if (this.InvokeRequired == true)
{
EventHandler eh = new EventHandler(Form1_SomeEvent);
this.Invoke(eh, new object[] { null, null });
}
else
{
label1.Text = "Counter value: " + _counter++;
}
}
void Form1_CloseGUI(object sender, EventArgs e)
{
if (this.InvokeRequired == true)
{
EventHandler eh = new EventHandler(CloseGUI);
this.Invoke(eh, new object[] { null, null });
}
else
{
_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:
void Form1_SomeEvent(object sender, EventArgs e)
{
if (this.InvokeRequired == true)
{
EventHandler eh = new EventHandler(Form1_SomeEvent);
try
{
this.Invoke(eh, new object[] { null, null });
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
else
{
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.