Form-closing race condition (part 2)
In my previous post, I mentioned that it turns out that there is a way for a form to be closed without the ...Closing/...Closed methods/events being called. Any code (including the synchronized flag technique I described earlier) that relies on this notification will fail under that scenario.
So, what to do? Well, as I mentioned before, one solution is simply to not depend on those methods or events. For example, in the "invoking/closing race condition" scenario where I started this whole thing, it's reasonable to just allow an exception to occur if the worker thread loses the race, catching and ignoring it. But sometimes, that's not an option.
In those situations, it's useful to know how to take things into your own hands. The problem is that when you pass a form instance to Application.Run, when that form is closed, the Application class interrupts the Run method's message loop and closes all the windows owned by the thread. Without the message loop running, when the windows associated with other forms are closed, those forms never get to process their close-related methods and events.
To fix this, we simply need to do basically the same work, but change the order of operations so that when the other forms are closed, they still get to do their close-related processing. The basic technique looks like this:
while (Application.OpenForms.Count > 0)
The call to Application.ExitThread is necessary to cause the Application.Run method to exit.
The logic can be put in one of two places. Either in the main form's own OnFormClosed method, or in the Program class as an event handler. Either way is fine, but you might prefer doing it as an event handler in the Program class if you would prefer for the main form to not have to know its relationship to the UI generally. This would be especially useful in scenarios where more than one form class might be used as the "main" form, but it's a nice abstraction in any case.
Using the latter approach as our example, if we assume that the above code is already in an event handler named _CloseHandler, then in the Program.Main method instead of something like this (the default from the IDE):
You'd have something like this:
Form formShow = new Form1();
formShow.FormClosed += _CloseHandler;
And that's all there is to it.