Timers and Windows - not firing if set to mode two from within a running Thread?

I have a thread that loops through a database lookup that can run through from 10’s to 10,000’s of records. To pacify the user, I display a counter and a progress bar on the main page of the app. The Thread kicks off a timer on the window that is set to 250ms and the timer has very simple code that updates the text and value of the progress bar - two items.

On Linux and macOS, this works exactly as expected. On Windows, however, the Timer is set to mode 2, but it never fires. Breakpoints are not obeyed (even the actual Break command), so the only thing that I can assume is that the timer is never firing after the thread sets it to mode 2. Checking the mode in teh debugger does show it is set to 2.

I’ve seen other discussions in the forum about low latency, but 250ms is far above the 15ms minimum for a Xojo Timer, and I’ve seen discussions about mode 1 timers never stopping. Nothing seems to visit this issue.

Anyone have an idea?

Christian Schmitz , would your TimerMBS be a better option here and do you have an example to replace a standard Xojo Timer object?

I’ve now added a syslog line at the end of the loop and I get 100’s of lines like this:

2018-09-23 14:15:40:Globals.LoadArchives: Timer to refresh load progress mode is 2

I can obviously verify that the Timer is set to Multiple through the entire run of the thread, but the timer itself never fires its Action event.

So you only see those messages when the loop completes?

Correct.

Do you have any #pragmas in the Thread.Run?

Does this work for you?

Are you doing things in a similar way?

https://www.dropbox.com/s/k8874ufjf45xa3r/TestThreadForTim.xojo_binary_project?dl=1

That’s exactly what I’m doing. The loop in the thread is a bit longer, but the principle is the same. I set the timer mode, enter the loop setting the two variables used by the timer.

I also have the Thread priority set to 5 and no pragmas or sleeps in the loop.

@Tim Jones — have you tried calling Reset after changing the mode?

The only thing I can thing of that will be breaking your timer will be if you have too many events being triggered which is blocking everything up.

I’d check that you haven’t got anything in your paints like altering setting of the painted item, i.e. you can alter listbox settings in paints which causes further paints which cause this exact issue where it gets into recursion and blocks events (and ultimately timers).

The quickest way to check that is to put break points in your paints and see how often they are firing, they should stop painting once the form has settled down, if they are firing more often then something is wrong.

Failing that, let me know and I’ll dig out some links of tools that will show you all the events flying around your app so you can see if you are getting a log jam or not.

Sorry to ask the stupid question, but you’re sure the code is running in a thread and not in the main thread, right? I only ask because I’ve done it before, calling a method in the thread directly, instead of through thread.run.

Could it be that easy? That’s what I do - in the Thread.Run, I call out to a method. Under Linux and macOS that seams to work as I would expect. Is it possible that the thread is not owning the global method that I’m calling?

I have the record loading in a method because the user can also manually call that method in an interactive mode to refresh the cache state if other clients have updated the environment.

Thead/timer combos are sometimes very stubborn.

Does the task example work for you? I think it’s rather convoluted but it works.

Make a simple example of your code and post it here.

Try without a fixed timer. In my app I send a notification from the database code to the progress window. There once a second the progress bar updates with an MBS function to bypass the timer.

No, if you call a global method from the Run event of a thread, it still runs in the thread. What I did was add a method to the thread subclass and then call it directly.

dim thd as new ThreadClass
thd.doSomething    // runs in the main thread

instead of starting the thread properly

dim thd as new ThreadClass
thd.Run

[quote=407098:@Tim Hare]No, if you call a global method from the Run event of a thread, it still runs in the thread. What I did was add a method to the thread subclass and then call it directly.

dim thd as new ThreadClass
thd.doSomething // runs in the main thread
instead of starting the thread properly

dim thd as new ThreadClass
thd.Run[/quote]

Damn it, maybe I do that too, I have to check, thank you Tim!

[quote=407097:@Beatrix Willius]Thead/timer combos are sometimes very stubborn.

Does the task example work for you? I think it’s rather convoluted but it works.[/quote]

I use that for another app and it works. Not as granularly as on Linux and macOS, but it’s at least fast enough for a once-a-second update in that case.

The Timer - LoadingArchives is False once all of the records have bee progressed:

If LoadingArchives Then WMain.ccMainTools1.prArchiveLoad.Value = ArLoadCnt WMain.ccMainTools1.prArchiveLoad.Invalidate WMain.ccMainTools1.lLoadingArchives.Text = kLoadingArchives + " (" + Format(ArLoadCnt, "#,") + " of " + Format(arcCount, "#,") + ")" Else // load completed WMain.ccMainTools1.lLoadingArchives.Invalidate WMain.ccMainTools1.lLoadingArchives.Visible = False WMain.ccMainTools1.prArchiveLoad.Visible = False WMain.ccMainTools1.cvToolButton(7).Enabled = True WMain.ccMainTools1.cvToolButton(1).Backdrop = Restore32x32 WMain.ccMainTools1.lRestore.TextColor = &c2F2F2F00 WMain.ccMainTools1.cvToolButton(8).Enabled = True WMain.ccMainTools1.cvToolButton(2).Backdrop = Verify32x32 WMain.ccMainTools1.lVerify.TextColor = &c2F2F2F00 Me.Mode = 0 End If

The method called from the thread. theShell is setup at the start of the method, but is not part of the loop.

If arcCount <> 0 Then z = arcCount // loop through all of the archives getting the details Startloop = Ticks WMain.tmUpdateArLoadCount.Mode = 2 for ArLoadCnt = 0 to arcCount - 1 t = Ticks TimedOut = False Finished = False theResult = "" Call ReplaceLineEndings(theShell.ReadAll, EndOfLine) cmd = "show -m archives """ + arcIDs(ArLoadCnt) + """" theShell.WriteLine cmd Do theShell.Poll Finished = InStr(theShell.Result, "input:") <> 0 TimedOut = InStr(theResult, "ERROR: Operation timed out") <> 0 Loop Until Finished or TimedOut theResult = ReplaceAllB(ReplaceAllB(ReplaceAllB(ReplaceLineEndings(theShell.ReadAll, EndOfLine), "input:", ""), EndOfLine, ""), cmd, "") // show -m archives 472de02107bb // ('Nov 04 15:10:54 2007', 'Bob', 'Offsite Backups', 'Full', ['A00007L2'], [13711], 'admin') If theResult <> "" Then If TimedOut Then Dim md As New MessageDialog md.Message = kServerCommProblemMsg md.Explanation = kServerCommProblemExp1 + BSServer + kServerCommProblemExp2 + Str(LongTimeout) + kServerCommProblemExp3 md.ActionButton.Caption = kOK Call md.ShowModal theShell.WriteLine "exit" theShell.Close theShell = Nil Return ElseIf CancelArchiveLoad Then Redim MyArchives(-1) theShell.WriteLine "exit" theShell.Close theShell = Nil Return End If archiveList.Append arcIDs(ArLoadCnt) + ": " + theResult End If Next EndLoop = ticks LoadingArchives = False For x = 0 To tickmarks.Ubound Next GenArchives archiveList End If

Again, this is very smooth under both Linux and macOS.

I’ll think through that.

As a second thought, would using the Xojo.Core.Timer.CallLater() be a saner option for something like this?

What mode are you running the shell in?

Mode 2 (Async) no other mode would work for this.

Just checking, as I didn’t see it in the code :slight_smile:

You don’t set that inside a #if that doesn’t run on windows so it defaults back to 0 which would cause this issue?

Nope - aside from the head of the LoadArchives method, that’s the whole of the method. Aside from the Dim block, this is the startup code before the loop.

LoadingArchives = True
Debug.Print CurrentMethodName + ":########## Loading Archives: Enter"
ReDim MyArchives(-1)
theShell.Mode = 2
#If Not TargetWin32
  theShell.Execute "nice -n 19 """ + bruservercmd + """  -v " + BSUser + " " + BSServer
  For x = 0 To 9
    theShell.Poll
  Next
  theShell.WriteLine AuthPassword
  For x = 0 To 9
    theShell.Poll
  Next
#Else
  theShell.Timeout = -1 // why isn't this the default in Windows???
  theShell.Execute """" + bruservercmd + """ -v " + BSUser + " " + BSServer
  For x = 0 To 9
    theShell.Poll
  Next
  theShell.WriteLine AuthPassword + EndOfLine
  For x = 0 To 9
    theShell.Poll
  Next
#EndIf
t = Ticks
TimedOut = False
Access = False
Do
  theShell.Poll
  Access = InStrB(theShell.Result, "input:") <> 0
  TimedOut =  InStr(theResult, "ERROR:  Operation timed out") <> 0
Loop Until Access or TimedOut
If TimedOut Then
  Dim md As New MessageDialog
  md.Message = kServerCommProblemMsg
  md.Explanation = kServerCommProblemExp1 + BSServer + kServerCommProblemExp2 + Str(10) + kServerCommProblemExp3
  md.ActionButton.Caption = kOK
  Call md.ShowModal
  theShell.Close
  theShell = Nil
  LoadingArchives = False
  Return
End If

I’m not sure where your main loop is. Could the shell.poll block the timers?

Who knows what sane is? My solution does without the thread/timer spaghetti I had in place before.