Thread randomly hangs, analysis and solution

Scenario: The randomly freezing thread

I have a process that reads multiple text files line-by-line and updates a progress bar.

  • When looping, I ran AddUserInterfaceUpdate(“Progress”,value) every 50 loops (e.g. “If ( numLoops Mod 50 ) = 0 Then” ), and my thread would randomly freeze.
  • If I added a few System.DebugLog() messages, the process would last longer, but still eventually froze.

Whenever I broke into the debugger, the thread’s ThreadState said that it was Running.

  • But despite that status, if I tried to step through, the thread remained frozen (i.e. System.DebugLog lines immediately after the breakpoint were never output).

  • Initially, the symptoms resembled a recursive loop, and since I couldn’t break/step in the debugger, I added System.DebugLog messages to find where the process was freezing/hanging.

  • This proved that the thread would freeze randomly in different places, none of which made logical sense.

Cause: Too many update statements

After hours of analysis and research, I finally went into “guru meditation” and began to consider what was happening internally in the O/S:

  • Remember, I was calling the AddUserInterfaceUpdate() about every 50 loops (which was still 100’s of times a second).
  • In laymen’s terms, this adds the event to the “event stack,” which the O/S would allocate CPU time to “clearing out” when it deemed okay.
  • But every computer has buffers, and every buffer has limits.
  • I inferred that the random hangs of my thread were caused by rapid-firing the AddUserInterfaceUpdate(“Progress”,value) (too much too fast).
  • I tested this hypothesis, and was able to both reproduce the hanging problem, and reproduce the solution.

Solution: Slow down the update statements

  • The solution was painfully simple: I slowed the output of AddUserInterfaceUpdate() statements.
  • Initially, I was outputting about ever 50 loops: “If ( numLoops Mod 50 ) = 0 Then”
  • I simply slowed that to about every 500 loops: “If ( numLoops Mod 500 ) = 0 Then”
  • The progress bar still progresses nicely.
  • But the AddUserInterfaceUpdate() events are no longer filling the event buffer faster than it can be processed, so the thread never hangs.

Here is the method code I used:

Var progressDict As New Dictionary
Var loopOneMax As Integer = 500
Var loopTwoMax As Integer = 500
progressDict.Value("ProgressValue") = 0
progressDict.Value("ProgressTotal") = loopOneMax * loopTwoMax
Me.AddUserInterfaceUpdate(progressDict)
Var progVal As Integer
For i As Integer = 0 To loopOneMax
  System.DebugLog("This is output from the loop i (EYE): " + i.ToString )
  For j As Integer = 0 To loopTwoMax
    progVal = progVal + 1
    System.DebugLog("This is output from the loop j (JAY): " + j.ToString )
  Next j
  Me.AddUserInterfaceUpdate("ProgressValue" : progVal )
Next i

Again, hopefully this will help someone else avoid this frustration.

That is helpful information.

When I do such things I tend to make the updates based on change in time or change in percentage of completeness. Rather than a set number of iterations.

So for example keep a date/time of last update and test when it has moved on more than a set time interval.

Alternatively, if your progress bar is showing % complete, only update it when that number has increased by 1% or more. If you do it like this you’re likely to keep your updates to something that is visible and not constantly refreshing the same value over and over again.

3 Likes

I had reached the same conclusions … thank you!

Don’t use System.DebugLog from the inside of a thread, you will miss outputs that depends on the event loop processing to put your output in the IDE panel.

Write to a debug txt file for such cases and inspect that file.

I lived such experience. Missing outputs. Once I’ve removed the display dependency the values were there.

I wrote some TESTS (never production) that printed few debuglogs from the MAIN thread and quit after. Some, if not all values were not displayed. So I throw a DoEvents() BEFORE the QUIT so Xojo could update the debug panel. Should not be this way, but Xojo does not guarantee such queue (when queued) processing before ending, it just aborts it.

To be accurate, I was actually writing my debug output to a log file. But I referenced “System.DebugLog” in my post to avoid distracting the point. :smile: Thank you!

3 Likes