This is a bit similar to @Ian_Kennedy 's thread in April about threads being terminated on quit.
I have the case where the user wants to quit the app, which they can do by choosing the Quit menu item, clicking a Quit button, or closing the app’s main window, and some work they’ve done may need to be saved, which should happen at quit time. That work is done on a thread which needs to complete its work before the app quits.
So, in the app’s CancelClose event, I see whether a save is needed, and if so, trigger the thread to do the work and return True to cancel the Quit. I haven’t yet decided whether the saving thread or another which is monitoring it will then quit the app once the save is complete, but either way a thread needs to call Quit.
I tried with the montitoring thread calling Quit, but what happens in that the app becomes unresponsive but does not quit. I have to force-quit it.
So, can a thread safely call Quit?
Wondering whether the quit has to be done on the main thread, I added an event definition to the thread class, and raise that from within the thread. The handler for that just calls quit. This has made no difference - the app becomes unresponsive. I checked this through with the debugger and the code is executing as I intend - just not working.
I wouldn’t attempt to call Quit from a thread. I would signal that back to the main thread via AddUserInterfaceUpdate followed by killing the thread or set a property that instantiates a short-period timer.
I had a timer that monitored the thread and performed actions on thread completion. It removes the need for a monitoring loop, preventing main thread lockup.
In a timer you needed to use you should:
- Shut down the timer
- Use Timer.CallLater( 10, TheMethod )
That way the timer can’t be called multiple times and ends peacefully before TheMethod is called from the main thread.
If you have more than one window that needs to save on quit and the save process is quite long, you may wish to ask all the “Window is changed do you want to save” questions up front and then close the windows in turn. Otherwise the user can’t start the quit and then walk away.
I will definitely try the AddUserInterfaceUpdate approach. But what was wrong with my raising an event? That runs on the main thread.
The actual save work should be very quick.
If you RaiseEvent from a thread, the handling code is in the context of the thread. You have to push it to the main thread with a timer or AddUserInterfaceUpdate.
Ah, OK, thanks. That at least explains the identical behaviour.
Using AddUserInterfaceUpdate and just quitting there, worked. Thanks all.
This will not be a popular response and I don’t know if it will keep working in the future but I have a similar situation. The thread sets a global variable for “shouldQuit” to true. If the quit event in the app checks and finds that is set to true then it will loop in app.Doevents() until it is set again to false meaning the thread has completed. (or some timeout has been exceeded setup as necessary for your app or not)
Cancelling the quit works for things that follow the standard, you have an unsaved document, type exceptions to it, but did not work for me in situations like you describe. I know you’re not supposed to use doEvents in a regular desktop app at all as horrible things might happen. That being said that was the only reliable way I could find to make the coordination of such things work properly together. Be aware that if you use that as a solution it may do really odd or horrible things with future versions of Xojo, but that as far as I can tell it works fine as of this moment in this edge condition.
What I do is this: in app.CancelClose, I set a quitting flag, and then do a lot of saving of state that can be safely done there. One type of save requires starting a thread to do it. In that instance, I start it, then also start a Termination thread, and return True. The quit is therefore cancelled. If I didn’t need to do that type of save, I can return False.
On entry to app.CancelClose, if app.quitting is already set then I also return False.
Meanwhile the saving thread does its save and exits. The Termination thread waits until the saving thread has exited (just a simple loop sleeping for 100msec each time), and then does a quit() via AddUserInterfaceUpdate.
I’m curious about the need for a second thread, the Termination thread. Couldn’t the saving thread call AddUserInterfaceUpdate on its own? Or was there some problem doing it that way?
Only, I think, if I reorder the code in app.CancelClose to be sure that starting the saving thread is the last action taken in app.CancelClose. Otherwise if the saving thread was pretty nippy it might itself finish and call Quit before app.CancelClose had quite finished.
My work in the 70s and 80s often involved worrying about race conditions, but that was then and now I’m a lot older and slower of thought. Thanks for the coment; I’ll take a look tomorrow and see if there’s any good reason not to streamline that a bit.