Trouble with UI update during a loop

  1. ‹ Older
  2. 3 years ago

    Joe H

    23 Apr 2017 Pre-Release Testers San Francisco

    Michel

    Loops in a timer would block too, since the timer also runs on the main thread. I believe I have to fold my loops IN BETWEEN timer events to make this work now.

  3. Michel B

    23 Apr 2017 Pre-Release Testers, Xojo Pro
    Edited 3 years ago

    No no no, a timer would not block the main thread. You don't seem to understand that what blocks the UI with a loop is because the event does not end until the loop is over. If you don't put too much code in a timer event, it will relinquish fast enough to let the UI refresh.

    @Joe H it seems like a huge amount of overhead to push each of the 100k AddRows from the thread to the listbox via each via an individual timer event.

    Since when addrow is complex ? You don't need a plethora of timers to simply push each row sequentially.

    I would use a rowContent property where I would place the data to addrow from the thread, then simply have the timer fire a single event with in the Action event handler

    ListBox1.Addrow(rowContent)

    Seems rather simple to do.

  4. Norman P

    23 Apr 2017 Xojo Inc, Pre-Release Testers, Xojo Pro Seeking work. npalardy@great-w...

    @Joe H Hi Norman

    I don't mind using a thread, in fact I already have a thread preparing the data and then currently use a single timer event to hand the data off to the main thread to populate the listbox. It's that GUI update loop on the main thread that now no longer animates the UI.

    I was trying to avoid touching the listbox itself from the thread, since that's bad mojo. But it seems like a huge amount of overhead to push each of the 100k AddRows from the thread to the listbox via each via an individual timer event.

    Sounds like the only way forward is to fragment the update loop into smaller chunks and drive the UI via a timer...

    Thanks,
    Joe

    The TIMER in the thread WILL only execute its action when it gets a slice and it WILL be on the main thread
    But since it is also part of the thread it can read data from the thread & also touch the UI (ie/ addrows)

    So in reality the thread could add to an internal array of "things to be added" and the timer's action then takes that array and shoves them into the listbox and then the array is empty, and the timers action ends, the UI updates, and the thread can grab then next bunch to put on the UI

  5. Joe H

    23 Apr 2017 Pre-Release Testers San Francisco

    OK guys we're all saying basically the same thing. I understand the thread to timer paradigm, and use it in this app already to show progress while I CREATE the data in a thread. But to show progress while loading that data into an actual listbox, I was hoping there was a modern way of periodically being able to update the UI from a loop on the main thread. Apparently there isn't so I'll deal with it in some other way.

    Norman, can you confirm that I can't use Listbox.AddRow from a thread, even if I disable UI updates some way?

  6. Mattias S

    24 Apr 2017 Pre-Release Testers, Xojo Pro Europe (Sweden, Stockholm)
    Edited 3 years ago

    I am using the Set*ThreadSafeMBS functions provided by Christian for simple tasks (such as updating a progressbar or a Label).
    For more complex things, like updating a ListBox I do it like Norman and others have described; load up an array of objects and then let a Timer push it to the ListBox.

  7. Mattias S

    24 Apr 2017 Pre-Release Testers, Xojo Pro Europe (Sweden, Stockholm)

    @Joe H Norman, can you confirm that I can't use Listbox.AddRow from a thread, even if I disable UI updates some way?

    AFAIK you cannot even touch non-UI properties (like ListCount) from a Thread without triggering an Exception.

  8. Ulrich B

    24 Apr 2017 Pre-Release Testers, Xojo Pro Europe (Germany, Berlin) · xo...
    Edited 3 years ago

    @Mattias Söm AFAIK you cannot even touch non-UI properties (like ListCount) from a Thread without triggering an Exception.

    Correct. Every attempt to address GUI elements from a thread is a no-go. That’s not because Xojo wants to annoy you, it’s a restriction that was built into preemptive operating systems. While in former times threads were cooperative, which means they did only appear to be running parallel at the same time, but in reality a scheduler assigned time slices between them and the main thread, they are now preemptive and as such several threads could try to address the GUI at the same time which would create a mess. Therefore current operating systems simply forbid thread access to the GUI.
    You can, however, always use a thread for long-running tasks and use a Xojo.core.timer.calllater call with a delay of 0 to forward the thread result to the main thread and update the GUI from within the method the timer call addresses. If you have to forward more than one piece of data to the method, simply use a dictionary or array as the Auto property that calllater supports.

  9. Mattias S

    24 Apr 2017 Pre-Release Testers, Xojo Pro Europe (Sweden, Stockholm)

    @Ulrich B If you have forward more than one piece of data to the method, simply use a dictionary or array as the Auto property that calllater supports.

    Cool tip, thanks for that.

  10. Joe H

    24 Apr 2017 Pre-Release Testers San Francisco

    The issue is that updating the listbox itself takes a very long time, hence I'd like to show the user a progress indicator.

    Since the listbox is a GUI element it must be updated on the main thread. Previously I used a short but highly iterative loop on the main thread to update the listbox, and ProgressBar.Refresh allowed me to also show progress from that same loop. ProgressBar.Refresh no longer works.

    I can't simply move that same updating loop to a timer since it will block the UI for just as long and still can't show any progress while in that loop.

    Currently I'm thinking the only way to solve this is to segment the listbox updates into many batches of a few updates each and drive it with many timer events so the UI can update itself in between. I guess I can use a high frequency multiple event timer, have the timer action event exit after say 500 ms of loop processing, and then left the timer fire again to continue on with the loop for another 500 ms. The UI will become active with a half populated listbox, but I guess that's the least of my worries...

  11. Tim P

    24 Apr 2017 Pre-Release Testers Rochester, NY

    I'm not fully versed on your use (I have sort of been watching this thread, but not really) but you might want to consider the use of some kind of data source. Whether you use a dictionary, array, or your own class or whatever, separate the data from the interface.

    You could then use a timer to chunk data into the interface in manageable bits from a data source that was loaded from a thread (50 rows at a time, timer period 200ms for example). I haven't tested to see just how responsive this method might be while adding tons of data, so it's just an "off the top of my head" kind of suggestion.

  12. Joe H

    24 Apr 2017 Pre-Release Testers San Francisco

    Hi Tim

    Yes, I'm processing a large amount of data using a class wrapping an array of classes. I process them in a background thread and drive the progress indicators via timers as has been discussed here.

    But how to load the listbox with a large amount of data, and also show progress? The listbox must be updated from the main thread because its a GUI element, but using a single big loop on the main thread doesn't allow me to show any progress since ProgressBar.Refresh no longer works.

    A 200 ms timer basically introduces 200 ms of idle time which I think would significantly slow down the listbox loading. A timer can start code if the app is idle, but doesn't do anything if the app is already busy (neither start nor stop).

    I'm gravitating towards the idea of a 1 ms timer, where the timer's action event has a loop that reads items off of a 100k element array and adds them to the listbox, but after 500 ms the loop sets the progress indicators and exits the timer event (even if the loop isn't finished). This lets the main event loop run, including firing that timer again very quickly to resume processing for another 500 ms.

    Basically the short timer is just used as a way to start code that itself can track time and periodically exit and yield a small amount of time back to the main event loop.

    This seems like a workable approach, but I'm open to other ideas too....

    Each time I explain it, it helps me understand it better. :-)

  13. Tim H

    24 Apr 2017 Pre-Release Testers Portland, OR USA

    @Joe H The UI will become active with a half populated listbox, but I guess that's the least of my worries...

    Pop up a modal window to keep the user from mucking about until the process is done. Put the timer and progress bar on the modal, of course.

  14. Beatrix W

    24 Apr 2017 Pre-Release Testers, Third Party Store Europe (Germany)

    @Joe Huber: how many rows do you load? I've found the standard listbox of Xojo way too slow for larger amounts of data.

  15. Joe H

    7 May 2017 Pre-Release Testers San Francisco

    The number of rows can vary widely depending on the files being analyzed. Typically a few thousand, but sometimes a hundred thousand or more, and yes it can get very slow to load and sort. Hence why I want the progress indicators.

  16. Joe H

    7 May 2017 Pre-Release Testers San Francisco

    In case anyone is still following this... my eventual solution was to create a state machine on the main thread to manipulate the listbox and other UI elements and to drive it with a timer. The state machine tasks would run for a maximum of 500 ms and then return to the main event loop so the UI could update and the app remain responsive.

    I didn't like the idea of fragmenting my code into various timer events because that approach can become very hard to understand the code flow and context and thus hard to debug and maintain. But putting everything into one state machine preserves the context and logic flow in one place and yet allows portions of it to be triggered by very simple timer events. I basically took my original update code, split it into sections using Select Case. I use a timer to repeatedly call that method and use the current state to Select the right code to run.

  17. David C

    8 May 2017 Pre-Release Testers, Xojo Pro Derby, ITM

    I use the MOD command to update the UI within a for…next, do…loop or while…wend loop e.g.

    maxCounter = 1000000
    for myCounter As Integer = 0 to maxCounter
    …
       if myCounter mod 1000 = 0 or myCounter = maxCounter  then
          'update the UI
          …
       end if
    next

    This way it updates at three times:

    1. on the first run
    2. every 1000 (reduced if a slow process)
    3. on the last run
  18. Joe H

    8 May 2017 Pre-Release Testers San Francisco

    David, that's a good way to determine WHEN you'd LIKE the update to happen (although I like to go by elapsed time instead of loop count).

    But if that code is running in the main thread of a 64 bit application, none of those UI updates will actually happen until after your loop completes and this method Returns.

  19. David C

    8 May 2017 Pre-Release Testers, Xojo Pro Derby, ITM

    O contraire Joe!

    In the 'update the UI' section I update a couple of variables, one text and one a percentage. A timer that was launched just before the thread checks those two variables and updates a TextArea and a ProgressBar on the main page. If the Timer detects that the thread has finished it stops itself and updates the UI even more, if necessary.

  20. Joe H

    8 May 2017 Pre-Release Testers San Francisco

    David "if that code is running in the main thread of a 64 bit application"

  21. David C

    8 May 2017 Pre-Release Testers, Xojo Pro Derby, ITM

    No. I saw the word 'thread' and thought you meant a sub-thread.

or Sign Up to reply!