|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
AssumptionsKnowledge of .NET Windows Forms event handling and threading is assumed, including the use of the OverviewThis 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 The following is a diagram of what the problem looks like: ProblemThe following code shows how to reproduce the problem. 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, 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 SolutionThe 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, 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)
{
// 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 Next, Finally, the event handler Alternate ApproachThe following is another way to solve this issue with a lot less code, albeit the solution is not as elegant. Basically, a 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.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||