How to wait for web responses

Before my app closes, I want it to wait for all URLConnection requests to return.
I have a list of queued POSTs, each of which need to wait for the ContentReceived event before sending the next, so I want to wait until the queued posts have sent and responses returned (with a reasonable timeout) before the program closes.
Works fine while the program is doing things, but when it’s time to quit, I need the program to wait until the POSTs are done and responses received (or a reasonable timeout has been reached) before closing the program.

Any idea how to do this?

Cancel the close, show a waiting for conections to end, call a timer…

Add also the start time in the queue, In timer check if the queue is clear and remove timed out Connections, if clear quit the app.

Thank you, Ivan;
“Call a timer”? Can you post an example? I understand what you mean, but not quite sure how to implement.
I was thinking that the wait would be done in the App.CancelClose event…

The only way would be having an endless loop that will not give cpu time to your connectios and those will die.

Just define a timer object in the app, initialize it in the CancelClose and use AddHandler to asociate the Action event with your method

Tried that, didn’t work for some reason. It’s like events stopped happening…

What method? I’m waiting for the URLConnection.ContentReceived event.

if it was just a busy loop then yes that would likely have that effect

In Cancel Close if you determine there are connections waiting to complete

     if connectionsNeedToComplete then 
         dim t as new timer
         addhandler t.action, addressof timerhandler
         t.run // or whatever it is they are calling it now
 
        return true to cancel closing // you might close windows etc
    end if

the method you attach the timer to simply runs periodically and when all connections have completed it calls quit

or something close to this

Yeah, Don’t do that.

The method you atach to the timer event with AddHandler. Look for the AddHandler documentation and/or the example in the Advanced examples.

in that event you should clear the connection from the queue, in the timer method check if the queue is empty and close the app

What do I put in TimerHandler, and how do I get the program to wait (without blocking events) until all the events have completed (or a timeout is reached)?

How do I get the program to wait without blocking events?

in the handler ?
whatever code you need to periodically check and see if all connections are finished doing their thing
I have no means to offer anything more than the advice

and IF they are all done then you just call quit

the timer itself will simply run & continue to run until you call quit

events will still happen

the timer

set it up to fire continuously every 250 msec and then when you figure out all connections are complete call QUIT

the app will NOT quit when cancel close is called because you return true (ie/ DO NOT CLOSE)
events will still happen just like always because the main loop of the app is still running

EDIT :
heres a quick sample

adjust as necessary
theres a method I just hard wired to say there is work left to do
adjust that to your code to check if downloads are still pending and do not quit until they are all done

you might consider

  • hiding all windows when the app goes into this waiting state
  • disabling all menu items since the app is going to do its downloading and then quit

Thanks, @npalardy , I’ll have a look.

Hey @npalardy , I had a look at your sample, but it’s not quite the same, I think your example is just doing a slow process rather than waiting for events. My issue seems to be that events are being blocked.

Here’s the code that needs to be “fixed” by removing DoEvents. Any suggestions?
LogSocket.Messages is the queue of waiting requests to URLConnection. Each time the URLConnection.ContentReceived event fires, a message is removed from the queue.

This procedure is called in CancelClose:

Splash.Show

App.DoEvents // Splash screen will not appear without this

If LogSocket <> Nil Then
  FlushTimer = New Timer
  AddHandler FlushTimer.Action, AddressOf UpdateMsgCount
  FlushTimer.Period = 50
  FlushTimer.Mode = Timer.ModeMultiple
  
  FlushTimeout = 0
  Flushes = LogSocket.Messages.UBound
  
  While LogSocket.Messages.UBound > 0 And FlushTimeout < 100
    App.DoEvents // nothing happens without this
  Wend
  FlushTimer.Mode = Timer.ModeOff
  FlushTimer = Nil
End If

And here’s UpdateMsgCount:

If Splash.Visible Then
  If Flushes > LogSocket.Messages.UBound Then Splash.TextField1.Text = "Closing Program... " + Str(LogSocket.Messages.UBound, "000")
  FlushTimeout = FlushTimeout + 1
Else
  FlushTimer.Mode = Timer.ModeOff
  FlushTimeout = 100
  Splash.Close
End If

in a desktop app there is always a main event loop run by the framework
thats how timers etc can still function even when there are no windows etc

however, if you run a loop in cancel close then the events dont complete which is why you need the doevents

in cancel close return true (as mine did)

and move the code that checks flush timeout & logsocket.messages inot the timers action event (it will get called periodically)

and when you finally figure out all messages are done then in the timers action event (In your case updatemsgcount) QUIT

thats what the example was doing

Gotcha.
So start the timer in CancelClose and return true
Check whether the message queue is empty (or the timer has run long enough) in the timer action
When the queue is empty or the the timer has run long enough call Quit

So will Quit call CancelClose again or go straight to Close?
If it calls CancelClose, then I’ll need to have a flag in the timer action that says we’re done.

I see now that’s what you were doing, so I’ll try again.

Thanks!

yeah calling quit WILL call cancel close so you need a flag to know you have already done this once

I think thats in the example

1 Like

Thanks, @npalardy , got it working now without any doevents. My real issue was not realizing that cancelclose stops all events if it completes. It’s documented but I must have missed that, thinking that app.close would be where all events stop.

Here’s how I’d do it on the App object

Private Sub TriggerQuit()
  Quit
End Sub

Event CancelClose()
  If PendingConnections Then
    Timer.CallLater(2000, WeakAddressOf TriggerQuit)
    Return True
  End If
End Event

Assuming PendingConnections is something that returns true if there are connections that have not finished yet. This way when you call Quit, it’ll check if there are connections running. If there are, the quit will be cancelled and attempted again 2 seconds later.

You could show UI here as long as you keep track of wether or not the UI you need has been destroyed.

Personally I avoid CallLater like the plague
Mostly because if the object that holds the method being called later goes out of scope (like say a window) then you just crash unceremoniously

Yes, there are plenty of ways to shoot yourself in the foot with CallLater. Sure would be nice if that could be checked for before trying to fire the delegate.

In this case, it’s safe because App is never going out of scope. The only problem remaining with this use of CallLater is its tendency to fire a seemingly-random “dictionary was changed while iterating over it” exception.

What language is that?