I'm making a GUI application with windows forms in Visual Studio .NET 2010 using C#
.
I have multiple threads, for example:
Control.Invoke()
.
My goal: I want to make sure that form is closed properly when user presses (x) on the main form. That means I want my connection to close, all side threads to terminate etc.
I'm trying to use FormClosing
event.
There I set KillThreads flag to true and wait for side threads to terminate. There is a check in each, something like if(KillThreads)
return;
But there is an issue. If I try to wait for all threads to be terminated
for(;;)
if(ActiveSideThreads == 0)
break;
closeConnection();
the thread N3 freezes blocked by Invoke()
call;
Sure it is, because the main gui thread is in infinite loop.
So the form closing is delayed forever.
If I implement before-close operations as a thread procedure 4, and make a new thread in FromClosing event handler,
form closes immediately, before the thread 4 termination, and Invoke()
throws an error - t. 4 could not close thread 3 in time.
If I add Join(), Invoke()
calls are blocked again, and thread 3 is never terminated, so thread 4 waits forever.
What should I do? Is there some way to resolve this issue?
First off decide whether or not you really need to be using Invoke
at all. It is, and has been for a while now, my opinion that Invoke
is one of the most overused techniques for causing an action to occur on the UI thread after it has been initiated from a worker thread. An alternative is to create some type of message object in the worker thread describing the action that needs to be done on the UI thread and place it into a shared queue. The UI thread will then poll this queue on some interval using System.Windows.Forms.Timer
and cause each action to occur. This has several advantages.
Invoke
imposes.Of course, Invoke
is very useful and there are many reasons to keep using this approach. If you do decide to keep using Invoke
then read more.
One idea is to set the KillThreads
flags in the Form.Closing
event and then cancel closing the form by setting FormClosingEventArgs.Cancel = true
. You may want to let the user know that a shutdown has been requested and is in progress. You may want to disable some of the controls on the form so new actions cannot be initiated. So basically we are signaling that a shutdown has been requested, but are deferring the closing of the form until the worker threads have shutdown first. To do this you may want to start a timer that periodically checks to see if the worker threads have ended and if they have then you can call Form.Close
.
public class YourForm : Form
{
private Thread WorkerThread;
private volatile bool KillThreads = false;
private void YourForm_Closing(object sender, FormClosingEventArgs args)
{
// Do a fast check to see if the worker thread is still running.
if (!WorkerThread.Join(0))
{
args.Cancel = true; // Cancel the shutdown of the form.
KillThreads = true; // Signal worker thread that it should gracefully shutdown.
var timer = new System.Timers.Timer();
timer.AutoReset = false;
timer.SynchronizingObject = this;
timer.Interval = 1000;
timer.Elapsed =
(sender, args) =>
{
// Do a fast check to see if the worker thread is still running.
if (WorkerThread.Join(0))
{
// Reissue the form closing event.
Close();
}
else
{
// Keep restarting the timer until the worker thread ends.
timer.Start();
}
};
timer.Start();
}
}
}
The code above calls Join
, but it specifies a timeout of 0 which causes the Join
call to return immediately. This should keep the UI pumping messages.
I would have a top level exception handler in your thread that's performing the Invoke, that swallows the exceptions arising from Invoke
if it discovers KillThreads
is true. Then all you need is for the main thread to set KillThreads to true and allow the message loop to end.
My general technique, rather than a simple boolean variable, would be to use a ManualResetEvent
as the signal for other threads to abort. The main reason for doing this is if I have long-running non-UI threads that want to sleep sometimes. By using the event, I can replace calls to Thread.Sleep
with calls to Event.WaitOne
, which means the thread will always wake up immediately if it has to abort.
In either case though, I don't usually wait around for the threads to signal completion. I trust them to do so in a timely manner, and don't tend to end up with subsequent code that relies on the threads not running. If you do have such a circumstance, perhaps you could elaborate on what it is?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With