Xojo Terminates threads when application is quit

I have an interesting problem. My application has a thread attached to a window that performs the save operation for the data. In my windows CancelClose operation I check if the window is “Changed” (as in requires saving) and ask the user if they wish to save. I then trigger the save thread to do the work.

The problem is that CancelClose then returns false and the window instance gets ripped out from under the thread. The same things happens when the application is quit, the windows cancel close causes the thread to start saving. The application then kills the threads and windows leaving my data file corrupt.

What I need is a way of hanging the CancelClose function up until the Thread is finished. I thinking of investigating Semaphores or a Mutex but if anyone has advise it would be welcome.

CancelClose must set a flag, return true to cancel closing.
Then start work to save, and a timer to monitor it.
Then timer can later close window, when window is saved.

1 Like

Or use the Thread’s UI event (whose name escapes me at the moment) to clear the flag and close the window.

1 Like

Thanks, I get the idea. However, if I return true from CancelClose won’t the application fail to quit once I close the window.

CancelClose has an appQuitting param, so you can track that too and call Quit again.

The trick for you is if another Window legitimately tried to stop the Quit from happening.

It sounds like I would end up rebuilding the entire system for CancelClose all over again, which with multiple windows could be rather complicated.

What I have been thinking is:

Put a Semaphore on the window class. In the method that is called to initiate the thread Signal the semaphore and then start the thread. In the Cancel close I can also call Signal, which should fail and block the execution until the save complete method kicks in and Releases the lock on the Semaphore, allowing CancelClose to carry on normally. Terminating the application if no window has said otherwise.

My only concern locking the main thread up would likely cause the timers to stop working hanging my entire process up.

Another thought: create a Window subclass (or add to one you already use) a system that handles the CancelClose event for you, along with shared flags that will let a window legitimately cancel the Quit operation.

Also, unless I misunderstood your plan, if you are going to let the Semaphore block the main thread anyway, there is no reason to have this method in a Thread.

Yes, I was only going to do that in the cancel close event. Not the routine event. That was my other thought, If I’m calling from Cancel close forget the thread and just save from the main thread.

Just a have global flag maintaining your shutdown state being tracked. Like 0=not shutting down, 1=busy shutting down, 2 = done and ready to exit.

If you app receives a “quit” in state 0, you abort the quit, set the flag 1 and start you shutdown routine.
If you app receives a “quit” in state 1, you abort the quit, and do nothing. There’s a background routine running.
If you app receives a “quit” in state 2, quit.

Your “shutdown” will write and process everything as you need. At end of its tasks, mark the shutdown flag to 2, and a ask the app to quit again.

@Rick_A If there is more than one window saving I can’t just switch from State 1 to State 2. I would have to know they had all finished. I suppose I could look at the Windows() function to see how many windows are open. Either that or another App property counting the number of Windows that are closing down. Incremented as each starts and decremented when complete.

Off to investigate. Thanks for the suggestions. I’ll report back.

Yep. And it’s worse than that: In my app, saves can take a long time, so the user can Cancel the Save operation. Keeping track of all of this is a bit of a chore. But it is doable. You just have to think through all the scenarios:

  • one window
  • more than one window
  • some windows dirty, some not
  • user Cancels a save operation
  • dirty window never been saved at all, so it must trigger a Save As dialog…
  • app was quitting
  • app was not quitting

Etc.

How about designing the save solution so that it can be called from a thread or not. Then in the cancelClose event you perform a save without using the thread.

1 Like

@Sam_Rowlands Yes, It’s already that way. I was thinking that may be the simplest solution. In the CancelClose just call the save function in the main thread. That would make it synchronous.

@Mike_D I started doing it by returning True from the CancelClose event and running via a thread, allowing my standard mechanism to operate. The first problem problem I encountered was the fact that returning true in Window.CancelClose prevents any other windows from calling the CancelClose event.

1 Like

Right, this is one of the edge cases - I think in this case, if the window was closing and appQuitting was true, then the File Save thread needs to fire a timer when it finishes to call App.Quit again, so that the orderly window close process continues.

Yes, but that makes it all serial. It can be a long time to save (with a huge dataset) and I’d rather get the questions asked up front and then left to process. I’m looking into putting code into App.CancelClose to:

  • Close on all windows of my document class
  • Set a flag and return True to prevent application shutdown.
  • Set App.AllowAutoQuit to True
  • In each window do the save, process and close
  • if any window refuses to close set App.AllowAutoClose back to False and keep the window open.

There’s also the method of, from the CancelClose event, looping thru all windows, using the Window() function, tell every window that aren’t self to close (again) and then return true (essentially recreating one part of the quit process).
I’m not sure whether this is “safe”, but I’ve never encountered a problem.

Wouldn’t a sheet/modal dialog with a progress bar (even infinite) and a Cancel button be good for your case?

So far this is what I’ve done. It seems to work but I’ll do more testing.

I’ve put the following code into App.CancelClose, this is the first part of the process of shutting down the application and is called before any Window.Close is activated. In there I’ve make an array of the Windows of interest (in my case CSW class). This is vital as attempting to close a window in this loop would cause Window() function to reorder, shrink or otherwise change and mess the whole process up.

I’m also testing if the window has “Changed” in this loop. If none of them have that flag you may as well just allow the shutdown to continue normally.

If I have some work to do I set App.AllowCancelClose to true, which does the job of triggering the app to quit once the final window has completed its save and closed. At this point the App.CancelClose will get called again and we must detect that there’s no work to do and allow normal shutdown (aWindows.LastIndex = -1).

Function CancelClose() Handles CancelClose as Boolean
  Var nWindow As Integer
  Var nWindows As Integer = App.WindowCount
  Var aWindows() As CSW
  Var NoneChanged As Boolean = True
  
  For nWindow = 0 To nWindows - 1
    If App.Window( nWindow ) IsA CSW Then
      aWindows.Add CSW( App.Window( nWindow ) )
      If App.Window( nWindow ).Changed Then
        NoneChanged = False
      End If
    End If
  Next
  
  ' If none of the windows needed saving or there where no CSW windows open
  If NoneChanged Or aWindows.LastIndex = -1 Then
    ' Then just let the application Quit In the normal way
    Return False
    
  Else
    ' Set the app to automatically quit when the last window closes
    App.AllowAutoQuit = True
    
    ' Now attempt to close each window
    For nWindow = 0 To aWindows.LastIndex
      aWindows( nWindow ).Close
    Next
    Return True
    
  End If
End Function

The CSW window class has a CancelClose event that detects the “Changed” status and asks the usual “Don’t save, Cancel, Save” dialog. If they hit Cancel, or say save and then hit cancel on the save as dialog the App.AllowAutoClose gets set back to false. Don’t save just allows the window to close in the normal way. Clicking Save returns True from the CancelClose preventing the window from closing. The thread monitoring timer checks the App.AllowAutoClose = True and closes the window when the Save is complete.

Function CancelClose(appQuitting as Boolean) Handles CancelClose as Boolean
  #Pragma Unused appQuitting
  If Changed Then
    Var oDialog As New CloseCancelDialog
    Select Case oDialog.ShowModalWithin( Self )
      
    Case CloseCancelDialog.Cancel
      ' They pressed Cancel on the Closing dirty window dialog
      App.AllowAutoQuit = False
      Return True
      
    Case CloseCancelDialog.Save
      ' They pressed save on the Closeing dirty window dialog
      If SaveTable( False, True ) Then
        Return True
      Else
        ' They pressed the cancel button on the SaveAs Dialog so we're done with Quitting
        App.AllowAutoQuit = False
        Return True
      End If
      
    Case CloseCancelDialog.DontSave
      ' They pressed don't save, continue to close and quit if that's whats happening
      Return False
      
    End Select
  Else
    Return False
  End If
End Function

I do have a sheet progress dialog but it only shows after a delay. That was to allow small datasets to save rapidly without the progress dialog popping open and closing straight away. I suppose I could rig it using appQuitting to show it immediately.

So I’ve worked up two methods so far and each has it’s advantages:

  1. Show a modal dialog in a function called from CancelClose. The modal prevents the CancelClose ripping the dialog out from under you, while the thread keeps going performing the save, a timer keeps the dialog progress bar visible and updated.

  2. Take control of App.CancelClose and return True. Initiate a close on each window and save as appropriate from the Window.CancelClose event returning True to prevent window closing. Once the save is complete close the window via code. Using the App.AllowAutoQuit to shut down the application when complete. Obviously you need your own flag if you want to target Windows / Linux as these have AllowAutoClose True normally.

Option 1
Effectively make the standard shutdown procedure work correctly, however the Modal dialog / sheet causes the main thread to suspend, apart from the timer it would seem. This prevents other windows from asking if you want to save the contents until the prior window is completed.

The problem with this approach is that the interaction is is all spread out. If five windows need to be saved you don’t get asked about number 2 until number one is saved. then number 3 after 2 is saved etc. If it takes a long time for one of the applications your user ends up waiting around to get asked about saving.

Option 2
The nice thing about option 2 is it solves the problem mentioned in option 1. The user is prompted upfront for each window that requires a decision.

The down side is that the sequencing is lost. Saying Cancel to window 1 doesn’t prevent the dialog from appearing on other windows. So you end up having the Cancel the quit in any number of windows.

Also because you’ve returned True from App.CancelClose the Application menu is looses it’s highlight, removing the hint to the user that the application is quitting. I can always put it in my progress dialog I suppose.

Neither option is ideal and I’m not sure what one I’ll go with. Thanks for all the input, it was very helpful.

you could also use the Window.Changed flag, usually you will not close the window if something was changed because you will save it first. its also useful for displaying a warning or dialog message.
maybe you can use this flag for CancelClose
its also easy to create a own control with visual changed tracking + a flag. subclassed canvas as example.

Forum for Xojo Programming Language and IDE. Copyright © 2021 Xojo, Inc.